Compare commits

...

41 commits

Author SHA1 Message Date
Vadim
b043d5cf0b use reusable workflow in "Create new release" 2024-02-21 16:43:34 +01:00
Vadim
d4cc799405 typo 2024-02-21 15:05:34 +01:00
Vadim
1eb6a672b7 removed version from build workflows 2024-02-21 14:54:52 +01:00
Vadim
c40ea9411f refined build workflows 2024-02-21 14:50:15 +01:00
Vadim
c852af2855 Merge branch 'main' of https://github.com/vodemn/m3_lightmeter into cd 2024-02-21 13:30:10 +01:00
Vadim
134af8ad28
ML-141 Prepare iOS release (#144)
* implemented `MockCameraContainerBloc` to stub camera on simulator

* [iOS] fixed camera preview aspect ratio

* place screenshots in platform-specific folders

* [iOS] updated buildable name

* [iOS] fixed stub image cover fit

* [iOS] implemented screenshots generator for all target devices

* store screenshots in _generated_ folder

* Update .gitignore

* Created "Build Prod .ipa" workflow

* added. certs to .ipa workflow

* test ipa building

* fixed provision cert path

* set provision profile in XCode

* set automatic signing for dev builds

* set ios version in Podfile

* renamed provision file

* renamed provision profile

* fixed cert folder...

* changed provision path

* typo

* typo

* try automatic signing

* use manual profile installation

* added export options

* typo

* increased timeout

* increased ipa timeout

* Update README.md

* typo

* [iOS] separated camera handling logic

* [iOS] fixed vibration

* migrated to http server iap

* [iOS] fixed histogram

* replaced distribution profile with development profile

* removed constants from env to the separate file

* removed duplicate launch schema

* fixed PR check workflow

* [iOS] set `ITSAppUsesNonExemptEncryption` to NO

* [iOS] removed java reference from "Build .ipa" workflow
2024-02-21 12:33:25 +01:00
Vadim
a1ce17d675
ML-154 Improved EXIF errors reporting (#159)
* removed unused analytics event & added `logCrash`

* added analytics to `RemoteConfigService`

* run app with `runZonedGuarded`

* added crash logging to `CameraContainerBloc`

* log product id for IAP errors

* typo

* log crashes in `RemoteConfigService`

* ignore silent `FlutterError`

* fixed `evFromImage` test

* fixed `showBuyProDialog` test

* log errors in console

* depend on iap 0.7.2

* Made errors non-fatal by default

* improved EXIF errors reporting

* fixed tests
2024-02-13 19:33:40 +01:00
Vadim
4f4b6cf1eb
ML-154 Made errors non-fatal by default (#158)
* removed unused analytics event & added `logCrash`

* added analytics to `RemoteConfigService`

* run app with `runZonedGuarded`

* added crash logging to `CameraContainerBloc`

* log product id for IAP errors

* typo

* log crashes in `RemoteConfigService`

* ignore silent `FlutterError`

* fixed `evFromImage` test

* fixed `showBuyProDialog` test

* log errors in console

* depend on iap 0.7.2

* Made errors non-fatal by default
2024-02-13 18:19:43 +01:00
github-actions[bot]
f965b99e1d Version bump 2024-01-27 22:34:29 +00:00
Vadim
fc37016770
ML-154 Improve Crashlytics reports (#155)
* removed unused analytics event & added `logCrash`

* added analytics to `RemoteConfigService`

* run app with `runZonedGuarded`

* added crash logging to `CameraContainerBloc`

* log product id for IAP errors

* typo

* log crashes in `RemoteConfigService`

* ignore silent `FlutterError`

* fixed `evFromImage` test

* fixed `showBuyProDialog` test

* log errors in console

* depend on iap 0.7.2
2024-01-27 23:20:53 +01:00
Vadim
9cb1cbaa90
ML-152 Added data extraction rules (#153) 2024-01-26 13:18:07 +01:00
github-actions[bot]
de50b03df9 Version bump 2024-01-25 19:22:11 +00:00
Vadim
5fe6c46fd7 Updated Pro features description 2024-01-25 20:04:14 +01:00
ScaredCube
7d0c6684d1
Fix Chinese Translation Errors (#151)
Fix translation bugs
Fix redundant letter "s"
Fix wrong Traditional Chinese
2024-01-25 09:53:15 +01:00
github-actions[bot]
d66404e085 Version bump 2024-01-15 22:28:33 +00:00
Vadim
85c409fbe8
ML-134 Firebase Remote Config issues (#150)
* added try-catch to config fetch
2024-01-15 23:13:26 +01:00
Vadim
2b2a5441c7
ML-130 Added ff for the "Pro features" tile on the main screen (#149)
* added ff for Pro features tile on main screen
2024-01-15 22:57:40 +01:00
Vadim
8f5893c7d2
ML-143 EV100 indication (#148)
* added `showEV100` to user preferences

* integrated EV100 setting to UI

* available for pro

* replaced `IAPProducts.isPurchased` with context extension

* fixed `UserPreferencesProvider` tests

* EV100 -> Ev100
2024-01-15 20:47:10 +01:00
Vadim
73d0c32323
Hide Pro features from the metering screen (#147)
* implemented `MockCameraContainerBloc` to stub camera on simulator

* hide pro features from metering screen

* disable pro features in settings

* use closed child background color in `AnimatedDialog`

* adjust `AnimatedDialogPicker` to items count

* close `AnimatedDialog` through context

* cleanup

* fixed `ReadingValueContainer` text color

* removed legacy translations

* fixed tests

* fixed `AnimatedDialog` scaling

* added `evFromImage` test

* added no EXIF test to `evFromImage`
2024-01-13 18:20:58 +01:00
nathan musoke
a2b4c88256
Fixed typo in reciprocity description (#142)
grater -> greater

Co-authored-by: Vadim <44135514+vodemn@users.noreply.github.com>
2024-01-13 17:42:23 +01:00
Vadim
c80bac23b2
Added Support section to README.md
- Fixed Development section numeration
- Added contact email link
2024-01-04 18:39:44 +01:00
Vadim
1b4be83dda
Update PRIVACY_POLICY.md 2024-01-04 16:52:56 +01:00
ScaredCube
55b0e52d7f
Fix Chinese Translation Errors (#140) 2023-12-01 14:54:44 +01:00
Vadim
561f849eea
Add app icon indicating dev build (#139)
* [Android] added (DEV) to dev flavor app name

* [Android] added `applicationIdSuffix` instead of explicit `applicationId`

* [Android] removed main/res for app to be able to see flavored resources

* replaced `flutter_launcher_icons` with `icons_launcher`

* [Android] generated icon for dev & prod flavors

* Create README.md

* [iOS] generated icons for dev & prod flavors

* [iOS] added (DEV) to dev flavor app name

* [iOS] cleanup
2023-11-21 21:37:23 +01:00
github-actions[bot]
e340327e32 Version bump 2023-11-14 12:30:33 +00:00
Vadim
19fc039723
ML-137 Dialogs improvements (#138)
* Force dialogs to have the same width

* Fix `DialogPicker` bouncing when the first selected item is near the end
2023-11-14 12:26:34 +01:00
Vadim
6566108994
ML-129 Spot metering (#136)
* imlemented `CameraSpotDetector`

* separated generic `DialogSwitch`

* added `CameraFeature` model

* added `CameraFeaturesListTile` to metering section

* added features dialog subtitles

* added long press to remove metering spot

* translations

* hide camera features for purchasable status

* hide `CameraHistogram` & `CameraSpotDetector` if purchasable

* bumped iap version

* fixed tests

* removed redundant camera state emission

* tests

* Fixed remote config initalization

* updated pro features description
2023-11-11 21:05:11 +01:00
github-actions[bot]
ddc7ec8c8b Version bump 2023-11-07 11:15:17 +00:00
Vadim
434327a7d0 Disable list tile onTap if IAP is pending 2023-11-07 12:03:38 +01:00
Vadim
068834bfe5
ML-134 Firebase Remote Config issues (#135)
* Unable to connect to the server

* internal remote config fetch error

* fixed tests
2023-11-07 11:57:36 +01:00
Vadim
3bb3f12641
ML-62 Utils tests (#133)
* removed redundant `UserPreferencesService` from `MeteringBloc`

* wip

* post-merge fixes

* `MeasureEvent` tests

* `MeasureEvent` tests revision

* `MeasureEvent` tests added timeout

* added stubs for other `MeteringBloc` events

* rewritten `MeteringBloc` logic

* wip

* `IsoChangedEvent` tests

* refined `IsoChangedEvent` tests

* `NdChangedEvent` tests

* `FilmChangedEvent` tests

* `MeteringCommunicationBloc` tests

* added test run to ci

* overriden `==` for `MeasuredState`

* `LuxMeteringEvent` tests

* refined `LuxMeteringEvent` tests

* rename

* wip

* wip

* `InitializeEvent`/`DeinitializeEvent` tests

* clamp minZoomLevel

* fixed `MeteringCommunicationBloc` tests

* wip

* `ZoomChangedEvent` tests

* `ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent` tests

* renamed test groups

* added test coverage script

* improved `CameraContainerBloc` test coverage

* `EquipmentProfileChangedEvent` tests

* verify response vibration

* fixed running all tests

* `MeteringCommunicationBloc` equality tests

* `CameraContainerBloc` equality tests

* removed generated code from coverage

* `MeteringScreenLayoutFeature` tests

* `SupportedLocale` tests

* `Film` tests

* `CaffeineService` tests

* `UserPreferencesService` tests (wip)

* `LightSensorService` tests (wip)

* `migrateOldKeys()` tests

* ignore currently unused getters & setters

* gradle upgrade

* `reset(sharedPreferences);` calls count

* typo

* `MeteringInteractor` tests

* `SettingsInteractor` tests (wip)

* `MeteringInteractor` tests (wip)

* `SettingsInteractor` tests

* AnimatedDialog picker standalone tests

* Moved Animated dialog picker to widget tests

* `ExtremeExposurePairsContainer` widget test

* dialog picker test

* Match extreme exposure pairs & pairs list edge values

* `FilmPicker` widget tests

* fixed animated dialog picker tests

* add not hit files to coverage percentage

* Moved `EquipmentProfileProvider` & `FilmsProvider` to the main repo

* Synced _iap_ stub with repo

* `FilmsProvider` tests

* `EquipmentProfileProvider` tests

* Pass `availableFilms` to `FilmsProvider`

* `FilmPicker` tests

* removed unnecessary imports

* Metering layout features tests

* split integration tests by screens

* Films in use test

* mock light meter lux stream

* removed mockito mocks for integration tests

From no on these are the only mocks in use:
- Mock shared prefs initial values
- Mock platform responses (camera/light sensor)

* set sharedprefs mock without redundant group

* unified granting camera permission on Android

* fixed metering screen tests

* extracted common values

* `FilmPicker` integration tests

* fixed light sensor platform mocks

* wip

* removed integration tests for now

* moved screenshots generator to screenshots folder

* typo

* removed `MockIAPProductsProvider`

* implemented platform mocks for unit tests

* data/models/ 100% coverage

* `IsoValuePicker` tests

* `EquipmentProfileProvider` tests

* extended PR check timeout

* typo

* added storage action verification for `FilmsProvider` tests

* `UserPreferencesProvider` tests

* Update README.md

* added //coverage:ignore to `ServicesProvider`

* typo

* typo

* `toStringSignedAsFixed` tests

* `SelectableInheritedModel` tests

* removed unused `TextLineHeight` util

* `VolumeKeysNotifier` tests

* import

* `EquipmentProfileListener` tests

* typo

* split `EquipmentProfileListener` tests

* `showBuyProDialog` tests

* added `maybeOf` getter for iap stub
2023-11-02 17:40:47 +01:00
github-actions[bot]
37a3b79f04 Version bump 2023-11-01 10:16:41 +00:00
Vadim
d36db97959 Updated IAP version to fix network issue
https://github.com/flutter/flutter/issues/135540
2023-10-31 22:32:02 +01:00
Vadim
a52efcd341
ML-130 Integrate Firebase Remote Config (#132)
* implemented `RemoteConfigService`

* added alternative translations

* typo

* added `firebase_analytics`

* dim paid features list tiles

* log list tile tap instead of dialog

* implemented `RemoteConfigProvider`

* typo
2023-10-31 18:42:25 +01:00
Vadim
f3b08868be
ML-62 Providers tests + Platform & Application mocks (#131)
- Fixed test coverage calculation
- Removed `mockito` from the application mock
- Implemented platform channel mocks to mimic incident light metering
- Covered providers with unit tests
- Covered metering screen pickers with widget tests
- Laid foundation for integration tests
2023-10-20 16:12:43 +02:00
Vadim
0b51db642c
ML-126 Automate screenshots creation (#128)
* generate screenshots with ep set to None
2023-09-29 12:45:39 +02:00
Vadim
e0320b6704
ML-126 Automate screenshots creation (#127)
* Create screenshot_driver.dart

* wip

* deleted screenshots

* iap mock

* generate for 3 colors

* cleanup

* generate single dark screenshots

* snake_case

* added stub image for camera

* scroll to the first checkbox selected

* unstub iap

* cleanup

* Update generate_screenshots.dart

* typo
2023-09-28 23:29:33 +02:00
ScaredCube
5b1b0b0540
Update intl_zh.arb of new features (#125) 2023-09-23 12:52:58 +02:00
github-actions[bot]
79105ab4f1 Version bump 2023-09-20 10:31:50 +00:00
Vadim
2a3c6b0b09
Added user feedback label to issue templates 2023-09-20 12:11:23 +02:00
Vadim
cc660de0c4
Fixed PR check (#122)
* updated stub script to work with tags

* depend on step conclusion

* check PR number
2023-09-20 11:58:04 +02:00
249 changed files with 5909 additions and 1579 deletions

View file

@ -2,7 +2,7 @@
name: Bug report
about: Create a bug report to help improve the app
title: ''
labels: bug
labels: bug, user feedback
assignees: vodemn
---

View file

@ -2,19 +2,16 @@
name: Feature request or improvement
about: Suggest an idea for this project
title: ''
labels: feature
labels: feature, user feedback
assignees: vodemn
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the feature or the problem it solves**
A clear and concise description of what the problem is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

14
.github/scripts/restore_from_base64.sh vendored Normal file
View file

@ -0,0 +1,14 @@
content="$1"
filename="$2"
if [[ ! -n "$content" ]]; then
echo "Provide file content"
exit 1
fi
if [[ ! -n "$filename" ]]; then
echo "Provide a path to an output file"
exit 1
fi
echo -n "$content" | base64 --decode --output "$filename"

View file

@ -3,27 +3,49 @@
# separate terms of service, privacy policy, and support
# documentation.
name: Build .apk
name: Build Android
run-name: Build Android${{inputs.stage-backend && ' (Stage)' || '' }}
on:
workflow_call:
inputs:
version:
description: "Version"
required: false
type: string
build-number:
description: "Build number"
required: false
type: string
stage-backend:
description: "Use stage backend"
required: true
type: boolean
workflow_dispatch:
inputs:
flavor:
description: 'Flavor'
type: choice
version:
description: "Version"
required: false
type: string
build-number:
description: "Build number"
required: false
type: string
stage-backend:
description: "Use stage backend"
required: true
options:
- dev
- prod
default: 'dev'
include-iap:
type: boolean
description: Include IAP package
default: true
env:
VERSION: ${{ github.event.inputs.version }}
BUILD_NUMBER: ${{ github.event.inputs.build-number }}
BUILD_OVERRIDES: ${{ github.event.inputs.version != '' && '--build-name=$VERSION' || '' }} ${{ github.event.inputs.build-number != '' && '--build-number=$BUILD_NUMBER' || '' }}
BUILD_ARGS: --release --flavor prod --target lib/main_prod.dart $BUILD_OVERRIDES
jobs:
build:
name: Build .apk
name: Build .apk & .aab
runs-on: macos-11
timeout-minutes: 15
steps:
@ -33,52 +55,35 @@ jobs:
- name: Connect private iap package
uses: webfactory/ssh-agent@v0.8.0
if: ${{ inputs.include-iap }}
with:
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
- name: Override iap package with stub
if: ${{ !inputs.include-iap }}
run: bash ./.github/scripts/stub_iap.sh
- uses: actions/setup-java@v2
- uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "11"
- name: Restore Android keystore .jsk and .properties files
env:
KEYSTORE: ${{ secrets.KEYSTORE }}
KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }}
run: |
KEYSTORE_PATH=$RUNNER_TEMP/keystore.jks
echo -n "$KEYSTORE" | base64 --decode --output $KEYSTORE_PATH
cp $KEYSTORE_PATH ./android/app
KEYSTORE_PROPERTIES_PATH=$RUNNER_TEMP/key.properties
echo -n "$KEYSTORE_PROPERTIES" | base64 --decode --output $KEYSTORE_PROPERTIES_PATH
cp $KEYSTORE_PROPERTIES_PATH ./android
bash .github/scripts/restore_from_base64.sh "${{ secrets.KEYSTORE }}" "android/app/keystore.jks"
bash .github/scripts/restore_from_base64.sh "${{ secrets.KEYSTORE_PROPERTIES }}" "android/key.properties"
- name: Restore android/app/google-services.json
env:
GOOGLE_SERVICES_JSON_ANDROID: ${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}
run: |
GOOGLE_SERVICES_JSON_ANDROID_PATH=$RUNNER_TEMP/google-services.json
echo -n "$GOOGLE_SERVICES_JSON_ANDROID" | base64 --decode --output $GOOGLE_SERVICES_JSON_ANDROID_PATH
cp $GOOGLE_SERVICES_JSON_ANDROID_PATH ./android/app
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}" "android/app/google-services.json"
- name: Restore firebase_options.dart
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
- name: Restore constants.dart
env:
FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
run: |
FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
cp $FIREBASE_OPTIONS_PATH ./lib
CONSTANTS: ${{inputs.stage-backend && secrets.CONSTANTS_STAGE || secrets.CONSTANTS }}
run: bash .github/scripts/restore_from_base64.sh "${{ env.CONSTANTS }}" "lib/constants.dart"
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: '3.10.0'
flutter-version: "3.10.0"
- name: Prepare flutter project
run: |
@ -86,13 +91,20 @@ jobs:
flutter pub get
flutter pub run intl_utils:generate
- name: Build .apk
env:
FLAVOR: ${{ github.event.inputs.flavor }}
run: flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_$FLAVOR.dart
- name: Build apk
run: flutter build apk $BUILD_ARGS
- name: Upload artifact
- name: Upload apk to artifacts
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_${{ github.event.inputs.flavor }}
path: build/app/outputs/flutter-apk/app-${{ github.event.inputs.flavor }}-release.apk
name: m3_lightmeter_apk
path: build/app/outputs/flutter-apk/app-prod-release.apk
- name: Build appbundle
run: flutter build appbundle $BUILD_ARGS
- name: Upload app bundle to artifacts
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_bundle
path: build/app/outputs/bundle/prodRelease/app-prod-release.aab

126
.github/workflows/build_ipa.yml vendored Normal file
View file

@ -0,0 +1,126 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Build iOS
run-name: Build iOS${{inputs.stage-backend && ' (Stage)' || '' }}
on:
workflow_call:
inputs:
version:
description: "Version"
required: true
type: string
build-number:
description: "Build number"
required: true
type: string
stage-backend:
description: "Use stage backend"
required: true
type: boolean
workflow_dispatch:
inputs:
version:
description: "Version"
required: false
type: string
build-number:
description: "Build number"
required: false
type: string
stage-backend:
description: "Use stage backend"
required: true
type: boolean
env:
VERSION: ${{ github.event.inputs.version }}
BUILD_NUMBER: ${{ github.event.inputs.build-number }}
BUILD_OVERRIDES: ${{ github.event.inputs.version != '' && '--build-name=$VERSION' || '' }} ${{ github.event.inputs.build-number != '' && '--build-number=$BUILD_NUMBER' || '' }}
BUILD_ARGS: --release --flavor prod --target lib/main_prod.dart $BUILD_OVERRIDES
jobs:
build:
name: Build .ipa
runs-on: macos-11
timeout-minutes: 60
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Connect private iap package
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
- name: Install the Apple certificate and provisioning profile
env:
APP_STORE_P12: ${{ secrets.APP_STORE_P12 }}
APP_STORE_P12_PASSWORD: ${{ secrets.APP_STORE_P12_PASSWORD }}
APP_STORE_PROVISION_PROD: ${{ secrets.APP_STORE_PROVISION_PROD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PROVISION_PATH=$RUNNER_TEMP/build_provision.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$APP_STORE_P12" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$APP_STORE_PROVISION_PROD" | base64 --decode -o $PROVISION_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$APP_STORE_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PROVISION_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- name: Restore ios/Runner/ExportOptions.plist
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.APP_STORE_EXPORT_OPTIONS }}" "ios/Runner/ExportOptions.plist"
- name: Restore firebase_options.dart
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
- name: Restore constants.dart
env:
CONSTANTS: ${{inputs.stage-backend && secrets.CONSTANTS_STAGE || secrets.CONSTANTS }}
run: bash .github/scripts/restore_from_base64.sh "${{ env.CONSTANTS }}" "lib/constants.dart"
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
- name: Prepare flutter project
run: |
flutter --version
flutter pub get
flutter pub run intl_utils:generate
- name: Build .ipa
run: flutter build ipa $BUILD_ARGS --export-options-plist=ios/Runner/ExportOptions.plist
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_$FLAVOR_ipa
path: build/ios/ipa/lightmeter.ipa
- name: Clean up keychain and provisioning profile
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_provision.mobileprovision

View file

@ -3,7 +3,6 @@
# separate terms of service, privacy policy, and support
# documentation.
# This workflow uses perl regex. For better syntaxis understading see these docs:
# https://perldoc.perl.org/perlrequick#Search-and-replace
# https://perldoc.perl.org/perlre#Other-Modifiers
@ -36,102 +35,14 @@ on:
description: Include IAP package
default: true
env:
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
jobs:
build:
name: Build .apk & .aab
if: ${{ inputs.github-release || inputs.google-play-release }}
runs-on: macos-11
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Connect private iap package
uses: webfactory/ssh-agent@v0.8.0
if: ${{ inputs.include-iap }}
with:
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
- name: Override iap package with stub
if: ${{ !inputs.include-iap }}
run: bash ./.github/scripts/stub_iap.sh
- uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "11"
- name: Restore Android keystore .jsk and .properties files
env:
KEYSTORE: ${{ secrets.KEYSTORE }}
KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }}
run: |
KEYSTORE_PATH=$RUNNER_TEMP/keystore.jks
echo -n "$KEYSTORE" | base64 --decode --output $KEYSTORE_PATH
cp $KEYSTORE_PATH ./android/app
KEYSTORE_PROPERTIES_PATH=$RUNNER_TEMP/key.properties
echo -n "$KEYSTORE_PROPERTIES" | base64 --decode --output $KEYSTORE_PROPERTIES_PATH
cp $KEYSTORE_PROPERTIES_PATH ./android
- name: Restore android/app/google-services.json
env:
GOOGLE_SERVICES_JSON_ANDROID: ${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}
run: |
GOOGLE_SERVICES_JSON_ANDROID_PATH=$RUNNER_TEMP/google-services.json
echo -n "$GOOGLE_SERVICES_JSON_ANDROID" | base64 --decode --output $GOOGLE_SERVICES_JSON_ANDROID_PATH
cp $GOOGLE_SERVICES_JSON_ANDROID_PATH ./android/app
- name: Restore firebase_options.dart
env:
FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
run: |
FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
cp $FIREBASE_OPTIONS_PATH ./lib
# This step makes sense when Github release is enabled because this release increments the build number.
# Therefore here we have to increment it as well to build an apk with the same build number.
- name: Increment build number & replace version number
if: ${{ inputs.github-release }}
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: '3.10.0'
- name: Prepare flutter project
run: |
flutter --version
flutter pub get
flutter pub run intl_utils:generate
- name: Build apk
if: ${{ inputs.github-release }}
run: flutter build apk $BUILD_ARGS
- name: Upload apk to artifacts
if: ${{ inputs.github-release }}
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_apk
path: build/app/outputs/flutter-apk/app-prod-release.apk
- name: Build appbundle
if: ${{ inputs.google-play-release }}
run: flutter build appbundle $BUILD_ARGS
- name: Upload app bundle to artifacts
if: ${{ inputs.google-play-release }}
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_bundle
path: build/app/outputs/bundle/prodRelease/app-prod-release.aab
uses: ./.github/workflows/build_apk.yml
secrets: inherit
with:
version: ${{ github.event.inputs.version }}
stage-backend: false
generate-release-notes:
name: Generate release notes

View file

@ -15,7 +15,7 @@ jobs:
analyze_and_test:
name: Analyze & test
runs-on: macos-11
timeout-minutes: 5
timeout-minutes: 10
steps:
- uses: 8BitJonny/gh-get-current-pr@2.2.0
id: PR
@ -36,6 +36,9 @@ jobs:
if: steps.fetch-iap.conclusion != 'success'
run: bash ./.github/scripts/stub_iap.sh
- name: Restore constants.dart
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
- uses: subosito/flutter-action@v2
with:
channel: "stable"

3
.gitignore vendored
View file

@ -58,5 +58,8 @@ android/app/google-services.json
ios/firebase_app_id_file.json
ios/Runner/GoogleService-Info.plist
/lib/firebase_options.dart
/lib/constants.dart
coverage/
test/coverage_helper_test.dart
screenshots/generated/

37
.vscode/launch.json vendored
View file

@ -5,59 +5,51 @@
"version": "0.2.0",
"configurations": [
{
"name": "dev-debug (android)",
"name": "dev-debug",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": [
"--flavor",
"dev",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
],
"program": "${workspaceFolder}/lib/main_dev.dart",
},
{
"name": "dev-profile (android)",
"name": "dev-profile",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"args": [
"--flavor",
"dev",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
],
"program": "${workspaceFolder}/lib/main_dev.dart",
},
{
"name": "prod-debug (android)",
"name": "prod-debug",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": [
"--flavor",
"prod",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
],
"program": "${workspaceFolder}/lib/main_release.dart",
"program": "${workspaceFolder}/lib/main_prod.dart",
},
{
"name": "prod-profile (android)",
"name": "prod-profile",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"args": [
"--flavor",
"prod",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
],
"program": "${workspaceFolder}/lib/main_release.dart",
"program": "${workspaceFolder}/lib/main_prod.dart",
},
{
"name": "dev-debug (ios)",
"name": "dev-simulator",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
@ -65,20 +57,7 @@
"--flavor",
"dev",
"--dart-define",
"cameraPreviewAspectRatio=3/4",
],
"program": "${workspaceFolder}/lib/main_dev.dart",
},
{
"name": "dev-profile (ios)",
"request": "launch",
"flutterMode": "profile",
"type": "dart",
"args": [
"--flavor",
"dev",
"--dart-define",
"cameraPreviewAspectRatio=3/4",
"cameraStubImage=assets/camera_stub_image.jpg"
],
"program": "${workspaceFolder}/lib/main_dev.dart",
},

View file

@ -7,17 +7,16 @@
"files.watcherExclude": {
"**/.fvm": true
},
"dart.lineLength": 100,
"dart.lineLength": 120,
"[dart]": {
"editor.rulers": [
100,
120,
],
"editor.selectionHighlight": true,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": false
"editor.wordBasedSuggestions": "off"
},
"dart.doNotFormat": [
"**/generated/**",

6
.vscode/tasks.json vendored
View file

@ -11,8 +11,6 @@
"--flavor",
"dev",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
"-t",
"lib/main_dev.dart",
],
@ -27,8 +25,6 @@
"--flavor",
"prod",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
"-t",
"lib/main_prod.dart",
],
@ -43,8 +39,6 @@
"--flavor",
"prod",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=240/320",
"-t",
"lib/main_prod.dart",
],

View file

@ -1,6 +1,6 @@
**Privacy Policy**
I, Vodemn, built the Material Lightmeter app as a Free app. This app is provided at no cost and is intended for use as is.
I, Vadim Turko, built the Material Lightmeter app as a Free app. This app is provided at no cost and is intended for use as is.
**Information Collection and Use**
@ -20,7 +20,7 @@ This app contains links to other sites. If you click on a third-party link, you
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
This policy is effective as of 2023-02-24
This policy is effective as of 2024-01-04
**Contact Us**

View file

@ -1,12 +1,15 @@
<img src="resources/social_preview.png" width="100%" />
![](https://github.com/vodemn/m3_lightmeter/actions/workflows/pr_check.yml/badge.svg)
![](https://github.com/vodemn/m3_lightmeter/actions/workflows/create_release.yml/badge.svg)
# Table of contents
- [Table of contents](#table-of-contents)
- [Backstory](#backstory)
- [Screenshots](#screenshots)
- [Development](#development)
- [Contribution](#contribution)
- [Support](#support)
- [iOS Limitations](#ios-limitations)
# Backstory
@ -33,7 +36,20 @@ Without further delay behold my new Lightmeter app inspired by Material You (a.k
To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install).
### 3. Project setup
### 2. Project setup
#### Restore _constants.dart_ file
Create a file _lib/constants.dart_ and paste the following content:
```dart
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_:
@ -66,29 +82,20 @@ flutter pub get
flutter pub run intl_utils:generate
```
### 4. (Optional) Install Firebase
### 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](https://firebase.google.com/docs/flutter/setup).
### 5. Build
### 4. Build
#### Android
- Checkout [Build .apk](.github/workflows/build_apk.yml) workflow for Android
- Checkout [Build .ipa](.github/workflows/build_ipa.yml) workflow for iOS
You can build an apk by running the following command from the root of the repository:
# Support
```console
flutter build apk --release --flavor dev --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_dev.dart
```
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues). To contribute to the project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
### iOS
TBD
# 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 feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
In case you have any other questions please contact me via [email](mailto:contact.vodemn@gmail.com?subject="Lightmeter").
# iOS Limitations

View file

@ -75,12 +75,13 @@ android {
flavorDimensions "app"
productFlavors {
dev {
applicationId "com.vodemn.lightmeter.dev"
resValue "string", "app_name", "Lightmeter (DEV)"
dimension "app"
signingConfig signingConfigs.release
applicationIdSuffix ".dev"
}
prod {
applicationId "com.vodemn.lightmeter"
resValue "string", "app_name", "Lightmeter"
dimension "app"
signingConfig signingConfigs.release
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#212121</color>
</resources>

View file

@ -3,7 +3,7 @@
package="com.vodemn.lightmeter">
<application
android:label="Lightmeter"
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
@ -14,6 +14,7 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:dataExtractionRules="@xml/data_extraction_rules"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
<exclude domain="sharedpref" path="FlutterSecureKeyStorage"/>
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
<exclude domain="sharedpref" path="FlutterSecureKeyStorage"/>
</device-transfer>
</data-extraction-rules>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#212121</color>
</resources>

11
assets/README.md Normal file
View file

@ -0,0 +1,11 @@
# Assets
## Launcher icons
### Android
Resources for Android are generated in Android Studio from the 512x512 source image as described in this [guide](https://developer.android.com/studio/write/create-app-icons).
### iOS
Resources for iOS are generated in XCode from the 1024x1024 source image as described in this [guide](https://developer.apple.com/documentation/xcode/configuring-your-app-icon).

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,34 +1,9 @@
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/films_provider.dart';
import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
export 'src/data/models/iap_product.dart';
export 'src/providers/equipment_profile_provider.dart';
export 'src/providers/films_provider.dart';
export 'src/providers/iap_products_provider.dart';
export 'src/data/iap_storage_service.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: FilmsProvider(
child: EquipmentProfileProvider(
child: child,
),
),
);
}
}
const List<Film> films = [];

View file

@ -0,0 +1,17 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class IAPStorageService {
const IAPStorageService(Object _);
String get selectedEquipmentProfileId => '';
set selectedEquipmentProfileId(String id) {}
List<EquipmentProfile> get equipmentProfiles => [];
set equipmentProfiles(List<EquipmentProfile> profiles) {}
Film get selectedFilm => const Film.other();
set selectedFilm(Film value) {}
List<Film> get filmsInUse => [];
set filmsInUse(List<Film> profiles) {}
}

View file

@ -6,8 +6,26 @@ enum IAPProductStatus {
enum IAPProductType { paidFeatures }
abstract class IAPProduct {
const IAPProduct._();
class IAPProduct {
final String storeId;
final IAPProductStatus status;
IAPProductStatus get status => IAPProductStatus.purchasable;
const IAPProduct({
required this.storeId,
this.status = IAPProductStatus.purchasable,
});
IAPProduct copyWith({IAPProductStatus? status}) => IAPProduct(
storeId: storeId,
status: status ?? this.status,
);
}
extension IAPProductTypeExtension on IAPProductType {
String get storeId {
switch (this) {
case IAPProductType.paidFeatures:
return "";
}
}
}

View file

@ -1,61 +0,0 @@
import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/src/providers/selectable_provider.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(
values: const [_defaultProfile],
selected: _defaultProfile,
child: widget.child,
);
}
void setProfile(EquipmentProfile data) {}
void addProfile(String name, [EquipmentProfile? copyFrom]) {}
void updateProdile(EquipmentProfile data) {}
void deleteProfile(EquipmentProfile data) {}
}
class EquipmentProfiles extends SelectableInheritedModel<EquipmentProfile> {
const EquipmentProfiles({
super.key,
required super.values,
required super.selected,
required super.child,
});
static List<EquipmentProfile> of(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.list)!.values;
}
static EquipmentProfile selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.selected)!.selected;
}
}

View file

@ -2,12 +2,15 @@ import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/src/data/models/iap_product.dart';
class IAPProductsProvider extends StatefulWidget {
final String apiUrl;
final Widget child;
const IAPProductsProvider({required this.child, super.key});
const IAPProductsProvider({required this.apiUrl, required this.child, super.key});
static IAPProductsProviderState of(BuildContext context) {
return context.findAncestorStateOfType<IAPProductsProviderState>()!;
static IAPProductsProviderState of(BuildContext context) => IAPProductsProvider.maybeOf(context)!;
static IAPProductsProviderState? maybeOf(BuildContext context) {
return context.findAncestorStateOfType<IAPProductsProviderState>();
}
@override
@ -18,7 +21,12 @@ class IAPProductsProviderState extends State<IAPProductsProvider> {
@override
Widget build(BuildContext context) {
return IAPProducts(
products: const [],
products: [
IAPProduct(
storeId: IAPProductType.paidFeatures.storeId,
status: IAPProductStatus.purchased,
)
],
child: widget.child,
);
}
@ -35,13 +43,27 @@ class IAPProducts extends InheritedModel<IAPProductType> {
super.key,
});
static IAPProduct? productOf(BuildContext context, IAPProductType type) => null;
static IAPProduct? productOf(BuildContext context, IAPProductType type) {
final IAPProducts? result = InheritedModel.inheritFrom<IAPProducts>(context, aspect: type);
return result!._findProduct(type);
}
static bool isPurchased(BuildContext context, IAPProductType type) => false;
static bool isPurchased(BuildContext context, IAPProductType type) {
final IAPProducts? result = InheritedModel.inheritFrom<IAPProducts>(context, aspect: type);
return result!._findProduct(type)?.status == IAPProductStatus.purchased;
}
@override
bool updateShouldNotify(IAPProducts oldWidget) => false;
@override
bool updateShouldNotifyDependent(covariant IAPProducts oldWidget, Set<IAPProductType> dependencies) => false;
bool updateShouldNotifyDependent(IAPProducts oldWidget, Set<IAPProductType> dependencies) => false;
IAPProduct? _findProduct(IAPProductType type) {
try {
return products.firstWhere((element) => element.storeId == type.storeId);
} catch (_) {
return null;
}
}
}

View file

@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/providers/films_provider.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:mocktail/mocktail.dart';
class _MockIAPStorageService extends Mock implements IAPStorageService {}
class MockIAPProviders extends StatefulWidget {
final List<EquipmentProfile> equipmentProfiles;
final String selectedEquipmentProfileId;
final List<Film> films;
final Film selectedFilm;
final Widget child;
const MockIAPProviders({
this.equipmentProfiles = const [],
this.selectedEquipmentProfileId = '',
this.films = mockFilms,
this.selectedFilm = const Film.other(),
required this.child,
super.key,
});
@override
State<MockIAPProviders> createState() => _MockIAPProvidersState();
}
class _MockIAPProvidersState extends State<MockIAPProviders> {
late final _MockIAPStorageService mockIAPStorageService;
@override
void initState() {
super.initState();
mockIAPStorageService = _MockIAPStorageService();
when(() => mockIAPStorageService.equipmentProfiles).thenReturn(mockEquipmentProfiles);
when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(widget.selectedEquipmentProfileId);
when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms);
when(() => mockIAPStorageService.selectedFilm).thenReturn(widget.selectedFilm);
}
@override
Widget build(BuildContext context) {
return EquipmentProfileProvider(
storageService: mockIAPStorageService,
child: FilmsProvider(
storageService: mockIAPStorageService,
availableFilms: mockFilms,
child: widget.child,
),
);
}
}
const defaultEquipmentProfile = EquipmentProfile(
id: '',
name: '',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
);
final mockEquipmentProfiles = [
EquipmentProfile(
id: '1',
name: 'Praktica + Zenitar',
apertureValues: ApertureValue.values.sublist(
ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)),
ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1,
),
ndValues: const [
NdValue(0),
NdValue(2),
NdValue(4),
NdValue(8),
],
shutterSpeedValues: ShutterSpeedValue.values.sublist(
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)),
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1,
),
isoValues: const [
IsoValue(50, StopType.full),
IsoValue(100, StopType.full),
IsoValue(200, StopType.full),
IsoValue(250, StopType.third),
IsoValue(400, StopType.full),
IsoValue(500, StopType.third),
IsoValue(800, StopType.full),
IsoValue(1600, StopType.full),
IsoValue(3200, StopType.full),
],
),
const EquipmentProfile(
id: '2',
name: 'Praktica + Jupiter',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
),
];
const mockFilms = [_MockFilm(100, 2), _MockFilm(400, 2), _MockFilm(3, 800), _MockFilm(400, 1.5)];
class _MockFilm extends Film {
final double reciprocityMultiplier;
const _MockFilm(int iso, this.reciprocityMultiplier) : super('Mock film $iso x$reciprocityMultiplier', iso);
@override
double reciprocityFormula(double t) => t * reciprocityMultiplier;
}

View file

@ -0,0 +1,59 @@
import 'dart:math';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:light_sensor/light_sensor.dart';
void setLightSensorAvilability({required bool hasSensor}) {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
LightSensor.methodChannel,
(methodCall) async {
switch (methodCall.method) {
case "sensor":
return hasSensor;
default:
return null;
}
},
);
}
void resetLightSensorAvilability() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
LightSensor.methodChannel,
null,
);
}
Future<void> sendMockIncidentEv(double ev) => sendMockLux((2.5 * pow(2, ev)).toInt());
Future<void> sendMockLux([int lux = 100]) async {
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
LightSensor.eventChannel.name,
const StandardMethodCodec().encodeSuccessEnvelope(lux),
(ByteData? data) {},
);
}
void setupLightSensorStreamHandler() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
MethodChannel(LightSensor.eventChannel.name),
(methodCall) async {
switch (methodCall.method) {
case "listen":
return;
case "cancel":
return;
default:
return null;
}
},
);
}
void resetLightSensorStreamHandler() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
MethodChannel(LightSensor.eventChannel.name),
null,
);
}

View file

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lightmeter/application.dart';
import 'package:lightmeter/application_wrapper.dart';
import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import '../mocks/paid_features_mock.dart';
import 'platform_channel_mock.dart';
extension WidgetTesterCommonActions on WidgetTester {
Future<void> pumpApplication({
IAPProductStatus productStatus = IAPProductStatus.purchased,
String selectedEquipmentProfileId = '',
Film selectedFilm = const Film.other(),
}) async {
await pumpWidget(
IAPProducts(
products: [
IAPProduct(
storeId: IAPProductType.paidFeatures.storeId,
status: productStatus,
),
],
child: ApplicationWrapper(
const Environment.dev(),
child: MockIAPProviders(
selectedEquipmentProfileId: selectedEquipmentProfileId,
selectedFilm: selectedFilm,
child: const Application(),
),
),
),
);
await pumpAndSettle();
}
Future<void> takePhoto() async {
await tap(find.byType(MeteringMeasureButton));
await pump(const Duration(seconds: 2)); // wait for circular progress indicator
await pump(const Duration(seconds: 1)); // wait for circular progress indicator
await pumpAndSettle();
}
Future<void> toggleIncidentMetering(double ev) async {
await tap(find.byType(MeteringMeasureButton));
await sendMockIncidentEv(ev);
await tap(find.byType(MeteringMeasureButton));
await pumpAndSettle();
}
Future<void> openAnimatedPicker<T>() async {
await tap(find.byType(T));
await pumpAndSettle(Dimens.durationL);
}
}
extension WidgetTesterListTileActions on WidgetTester {
/// Useful for tapping a specific [ListTile] inside a specific screen or dialog
Future<void> tapDescendantTextOf<T>(String text) async {
await tap(find.descendant(of: find.byType(T), matching: find.text(text)));
await pumpAndSettle();
}
}
extension WidgetTesterTextButtonActions on WidgetTester {
Future<void> tapSelectButton() => _tapTextButton(S.current.select);
Future<void> tapCancelButton() => _tapTextButton(S.current.cancel);
Future<void> tapSaveButton() => _tapTextButton(S.current.save);
Future<void> _tapTextButton(String text) async {
final button = find.byWidgetPredicate(
(widget) => widget is TextButton && widget.child is Text && (widget.child as Text?)?.data == text,
);
expect(button, findsOneWidget);
await tap(button);
await pumpAndSettle();
}
}

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -40,6 +40,12 @@ post_install do |installer|
# Start of the permission_handler configuration
target.build_configurations.each do |config|
# https://github.com/CocoaPods/CocoaPods/issues/12012
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
# Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',

View file

@ -44,7 +44,7 @@
8C539F8FF42AB22E298D5A5E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146EE1CF9000F007C117D /* Lightmeter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lightmeter.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
@ -95,7 +95,7 @@
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
97C146EE1CF9000F007C117D /* Lightmeter.app */,
);
name = Products;
sourceTree = "<group>";
@ -154,6 +154,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
45F53C083F2EA48EF231DA16 /* [CP] Embed Pods Frameworks */,
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */,
);
buildRules = (
);
@ -161,7 +162,7 @@
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productReference = 97C146EE1CF9000F007C117D /* Lightmeter.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -242,6 +243,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@ -282,6 +284,29 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\"",
"\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/\"",
"\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"",
"\"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)\"",
"\"$(PROJECT_DIR)/firebase_app_id_file.json\"",
);
name = "[firebase_crashlytics] Crashlytics Upload Symbols";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" ";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -370,18 +395,26 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = Lightmeter;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Lightmeter Development";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -499,18 +532,26 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = Lightmeter;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Lightmeter Development";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -522,18 +563,26 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = Lightmeter;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Lightmeter Development";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -599,18 +648,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = "Lightmeter (DEV)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -674,18 +728,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = "Lightmeter (DEV)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -746,18 +805,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = "Lightmeter (DEV)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View file

@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -45,7 +45,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -62,7 +62,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>

View file

@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -45,7 +45,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -62,7 +62,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BuildableName = "Lightmeter.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>

View file

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Icon square (dev).png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Icon square.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,008 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

Some files were not shown because too many files have changed in this diff Show more