mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 15:00:40 +00:00
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
This commit is contained in:
parent
a1ce17d675
commit
134af8ad28
34 changed files with 363 additions and 169 deletions
14
.github/scripts/restore_from_base64.sh
vendored
Normal file
14
.github/scripts/restore_from_base64.sh
vendored
Normal 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"
|
14
.github/workflows/build_apk.yml
vendored
14
.github/workflows/build_apk.yml
vendored
|
@ -67,12 +67,10 @@ jobs:
|
|||
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
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
|
||||
- name: Restore constants.dart
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
|
@ -89,10 +87,10 @@ jobs:
|
|||
- 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
|
||||
run: flutter build apk --release --flavor $FLAVOR -t lib/main_$FLAVOR.dart
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: m3_lightmeter_${{ github.event.inputs.flavor }}
|
||||
name: m3_lightmeter_${{ github.event.inputs.flavor }}_apk
|
||||
path: build/app/outputs/flutter-apk/app-${{ github.event.inputs.flavor }}-release.apk
|
||||
|
|
97
.github/workflows/build_ipa.yml
vendored
Normal file
97
.github/workflows/build_ipa.yml
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
# 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 Prod .ipa
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FLAVOR: "prod"
|
||||
|
||||
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
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.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 \
|
||||
--release \
|
||||
--flavor $FLAVOR \
|
||||
--target lib/main_$FLAVOR.dart \
|
||||
--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
|
12
.github/workflows/create_release.yml
vendored
12
.github/workflows/create_release.yml
vendored
|
@ -37,7 +37,7 @@ on:
|
|||
default: true
|
||||
|
||||
env:
|
||||
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
|
||||
BUILD_ARGS: --release --flavor prod -t lib/main_prod.dart
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -86,12 +86,10 @@ jobs:
|
|||
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
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
|
||||
- name: Restore constants.dart
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
|
||||
|
||||
# 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.
|
||||
|
|
3
.github/workflows/pr_check.yml
vendored
3
.github/workflows/pr_check.yml
vendored
|
@ -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
3
.gitignore
vendored
|
@ -58,7 +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/*.png
|
||||
screenshots/generated/
|
44
.vscode/launch.json
vendored
44
.vscode/launch.json
vendored
|
@ -5,83 +5,49 @@
|
|||
"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_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_prod.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev-debug (ios)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--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",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev-simulator",
|
||||
"request": "launch",
|
||||
|
@ -91,8 +57,6 @@
|
|||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=240/320",
|
||||
"--dart-define",
|
||||
"cameraStubImage=assets/camera_stub_image.jpg"
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -16,7 +16,7 @@
|
|||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||
"editor.suggestSelection": "first",
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.wordBasedSuggestions": false
|
||||
"editor.wordBasedSuggestions": "off"
|
||||
},
|
||||
"dart.doNotFormat": [
|
||||
"**/generated/**",
|
||||
|
|
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
|
@ -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",
|
||||
],
|
||||
|
|
26
README.md
26
README.md
|
@ -38,6 +38,19 @@ To build this app you need to install Flutter 3.10.0 stable. [How to install](ht
|
|||
|
||||
### 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_:
|
||||
|
||||
```yaml
|
||||
|
@ -75,17 +88,8 @@ Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics t
|
|||
|
||||
### 4. Build
|
||||
|
||||
#### Android
|
||||
|
||||
You can build an apk by running the following command from the root of the repository:
|
||||
|
||||
```console
|
||||
flutter build apk --release --flavor dev --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_dev.dart
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
TBD
|
||||
- Checkout [Build .apk](.github/workflows/build_apk.yml) workflow for Android
|
||||
- Checkout [Build .ipa](.github/workflows/build_ipa.yml) workflow for iOS
|
||||
|
||||
# Support
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@ 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) => IAPProductsProvider.maybeOf(context)!;
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ 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,
|
||||
|
@ -66,7 +70,12 @@ final mockEquipmentProfiles = [
|
|||
ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)),
|
||||
ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1,
|
||||
),
|
||||
ndValues: NdValue.values.sublist(0, 3),
|
||||
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,
|
||||
|
|
|
@ -63,6 +63,7 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -399,9 +399,11 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"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;
|
||||
|
@ -412,6 +414,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
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";
|
||||
|
@ -533,9 +536,11 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"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;
|
||||
|
@ -546,6 +551,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
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;
|
||||
|
@ -561,9 +567,11 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"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;
|
||||
|
@ -574,6 +582,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>Enc</key>
|
||||
<string></string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class HapticsService {
|
||||
|
@ -11,10 +13,17 @@ class HapticsService {
|
|||
|
||||
Future<void> _tryVibrate({required int duration, required int amplitude}) async {
|
||||
if (await _canVibrate()) {
|
||||
await Vibration.vibrate(
|
||||
duration: duration,
|
||||
amplitude: amplitude,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
await Vibration.vibrate(
|
||||
duration: duration,
|
||||
amplitude: amplitude,
|
||||
);
|
||||
} else {
|
||||
await Vibration.vibrate(
|
||||
pattern: [duration],
|
||||
intensities: [amplitude],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,39 +2,23 @@ enum BuildType { dev, prod }
|
|||
|
||||
class Environment {
|
||||
final BuildType buildType;
|
||||
final String sourceCodeUrl;
|
||||
final String issuesReportUrl;
|
||||
final String contactEmail;
|
||||
|
||||
final bool hasLightSensor;
|
||||
|
||||
const Environment({
|
||||
required this.buildType,
|
||||
required this.sourceCodeUrl,
|
||||
required this.issuesReportUrl,
|
||||
required this.contactEmail,
|
||||
this.hasLightSensor = false,
|
||||
});
|
||||
|
||||
const Environment.dev()
|
||||
: buildType = BuildType.dev,
|
||||
sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues/new/choose',
|
||||
contactEmail = 'contact.vodemn@gmail.com',
|
||||
hasLightSensor = false;
|
||||
|
||||
const Environment.prod()
|
||||
: buildType = BuildType.prod,
|
||||
sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues/new/choose',
|
||||
contactEmail = 'contact.vodemn@gmail.com',
|
||||
hasLightSensor = false;
|
||||
|
||||
Environment copyWith({bool? hasLightSensor}) => Environment(
|
||||
buildType: buildType,
|
||||
sourceCodeUrl: sourceCodeUrl,
|
||||
issuesReportUrl: issuesReportUrl,
|
||||
contactEmail: contactEmail,
|
||||
hasLightSensor: hasLightSensor ?? this.hasLightSensor,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
class PlatformConfig {
|
||||
const PlatformConfig._();
|
||||
|
||||
static double get cameraPreviewAspectRatio {
|
||||
final rational = const String.fromEnvironment('cameraPreviewAspectRatio').split('/');
|
||||
return int.parse(rational[0]) / int.parse(rational[1]);
|
||||
}
|
||||
static double get cameraPreviewAspectRatio => Platform.isAndroid ? 240 / 320 : 288 / 352;
|
||||
|
||||
static String get cameraStubImage => const String.fromEnvironment('cameraStubImage');
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/application_wrapper.dart';
|
||||
import 'package:lightmeter/constants.dart';
|
||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
|
@ -27,7 +28,10 @@ Future<void> runLightmeterApp(Environment env) async {
|
|||
products: [IAPProduct(storeId: IAPProductType.paidFeatures.storeId)],
|
||||
child: application,
|
||||
)
|
||||
: IAPProductsProvider(child: application),
|
||||
: IAPProductsProvider(
|
||||
apiUrl: iapServerUrl,
|
||||
child: application,
|
||||
),
|
||||
);
|
||||
},
|
||||
_errorsLogger.logCrash,
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -253,12 +254,29 @@ class _WidgetsBindingObserver with WidgetsBindingObserver {
|
|||
|
||||
_WidgetsBindingObserver(this.onLifecycleStateChanged);
|
||||
|
||||
/// Revoking camera permissions results in app being killed both on Android and iOS
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (_prevState == AppLifecycleState.inactive && state == AppLifecycleState.resumed) {
|
||||
return;
|
||||
switch (defaultTargetPlatform) {
|
||||
/// On Android opening a dialog results in [AppLifecycleState.inactive]
|
||||
case TargetPlatform.android:
|
||||
if (_prevState == AppLifecycleState.inactive && state == AppLifecycleState.resumed) {
|
||||
return;
|
||||
}
|
||||
_prevState = state;
|
||||
onLifecycleStateChanged(state);
|
||||
|
||||
/// When coming from the app's settings iOS fires paused -> inactive -> resumed state which falls into this condition.
|
||||
/// So the inactive state is skipped.
|
||||
case TargetPlatform.iOS:
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
return;
|
||||
}
|
||||
if (_prevState != state) {
|
||||
_prevState = state;
|
||||
onLifecycleStateChanged(state);
|
||||
}
|
||||
default:
|
||||
}
|
||||
_prevState = state;
|
||||
onLifecycleStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
|
@ -64,31 +65,58 @@ class _CameraHistogramState extends State<CameraHistogram> {
|
|||
histogramG = List.filled(256, 0);
|
||||
histogramB = List.filled(256, 0);
|
||||
|
||||
final int uvRowStride = image.planes[1].bytesPerRow;
|
||||
final int uvPixelStride = image.planes[1].bytesPerPixel!;
|
||||
|
||||
for (int x = 0; x < image.width; x++) {
|
||||
for (int y = 0; y < image.height; y++) {
|
||||
final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
|
||||
final int index = y * image.width + x;
|
||||
|
||||
final yp = image.planes[0].bytes[index];
|
||||
final up = image.planes[1].bytes[uvIndex];
|
||||
final vp = image.planes[2].bytes[uvIndex];
|
||||
|
||||
final r = yp + vp * 1436 / 1024 - 179;
|
||||
final g = yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91;
|
||||
final b = yp + up * 1814 / 1024 - 227;
|
||||
|
||||
histogramR[r.round().clamp(0, 255)]++;
|
||||
histogramG[g.round().clamp(0, 255)]++;
|
||||
histogramB[b.round().clamp(0, 255)]++;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
_yuv420toRgb(image);
|
||||
case TargetPlatform.iOS:
|
||||
_bgra8888toRgb(image);
|
||||
default:
|
||||
}
|
||||
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void _yuv420toRgb(CameraImage image) {
|
||||
final int uvRowStride = image.planes[1].bytesPerRow;
|
||||
final int uvPixelStride = image.planes[1].bytesPerPixel!;
|
||||
|
||||
for (int x = 0; x < image.width; x++) {
|
||||
for (int y = 0; y < image.height; y++) {
|
||||
final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
|
||||
final int index = y * image.width + x;
|
||||
|
||||
final yp = image.planes[0].bytes[index];
|
||||
final up = image.planes[1].bytes[uvIndex];
|
||||
final vp = image.planes[2].bytes[uvIndex];
|
||||
|
||||
final r = yp + vp * 1436 / 1024 - 179;
|
||||
final g = yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91;
|
||||
final b = yp + up * 1814 / 1024 - 227;
|
||||
|
||||
histogramR[r.round().clamp(0, 255)]++;
|
||||
histogramG[g.round().clamp(0, 255)]++;
|
||||
histogramB[b.round().clamp(0, 255)]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _bgra8888toRgb(CameraImage image) {
|
||||
for (int i = 0; i < image.planes.first.bytes.length; i++) {
|
||||
final int channel = i % 4;
|
||||
switch (channel) {
|
||||
case 3:
|
||||
break;
|
||||
case 2:
|
||||
histogramR[image.planes.first.bytes[i]]++;
|
||||
case 1:
|
||||
histogramG[image.planes.first.bytes[i]]++;
|
||||
case 0:
|
||||
histogramB[image.planes.first.bytes[i]]++;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HistogramChannel extends StatelessWidget {
|
||||
|
|
|
@ -84,7 +84,16 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (PlatformConfig.cameraStubImage.isNotEmpty) {
|
||||
return Image.asset(PlatformConfig.cameraStubImage);
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
PlatformConfig.cameraStubImage,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: _initializedNotifier,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/constants.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ReportIssueListTile extends StatelessWidget {
|
||||
|
@ -13,7 +13,7 @@ class ReportIssueListTile extends StatelessWidget {
|
|||
title: Text(S.of(context).reportIssue),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse(ServicesProvider.of(context).environment.issuesReportUrl),
|
||||
Uri.parse(issuesReportUrl),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/constants.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SourceCodeListTile extends StatelessWidget {
|
||||
|
@ -13,7 +13,7 @@ class SourceCodeListTile extends StatelessWidget {
|
|||
title: Text(S.of(context).sourceCode),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse(ServicesProvider.of(context).environment.sourceCodeUrl),
|
||||
Uri.parse(sourceCodeUrl),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/constants.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class WriteEmailListTile extends StatelessWidget {
|
||||
|
@ -13,8 +13,7 @@ class WriteEmailListTile extends StatelessWidget {
|
|||
leading: const Icon(Icons.email),
|
||||
title: Text(S.of(context).writeEmail),
|
||||
onTap: () {
|
||||
final email = ServicesProvider.of(context).environment.contactEmail;
|
||||
final mailToUrl = Uri.parse('mailto:$email?subject=M3 Lightmeter');
|
||||
final mailToUrl = Uri.parse('mailto:$contactEmail?subject=M3 Lightmeter');
|
||||
canLaunchUrl(mailToUrl).then((canLaunch) {
|
||||
if (canLaunch) {
|
||||
launchUrl(
|
||||
|
@ -29,7 +28,7 @@ class WriteEmailListTile extends StatelessWidget {
|
|||
action: SnackBarAction(
|
||||
label: S.of(context).copyEmail,
|
||||
onPressed: () {
|
||||
FlutterClipboard.copy(email).then((_) {
|
||||
FlutterClipboard.copy(contactEmail).then((_) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
});
|
||||
},
|
||||
|
|
|
@ -28,7 +28,7 @@ dependencies:
|
|||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: v0.7.2
|
||||
ref: v0.8.1
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
|
|
|
@ -10,8 +10,8 @@ As a user I want to see the most relevant screenshots in the store, so that I ca
|
|||
|
||||
- Metering screen
|
||||
|
||||
1. Reflected light metering mode*
|
||||
2. Incident light metering mode* **
|
||||
1. Reflected light metering mode\*
|
||||
2. Incident light metering mode\* \*\*
|
||||
3. Opened ISO picker
|
||||
|
||||
- Settings screen
|
||||
|
@ -24,14 +24,41 @@ As a user I want to see the most relevant screenshots in the store, so that I ca
|
|||
1. Just the screen
|
||||
2. Opened equipment profile ISO picker
|
||||
|
||||
> *also in dark mode
|
||||
> \*also in dark mode
|
||||
|
||||
> **Android only
|
||||
> \*\*Android only
|
||||
|
||||
## Run the generator
|
||||
|
||||
Screenshots will be stored in the _screenshots/generated/\<platform\>/_ folder.
|
||||
|
||||
### Android
|
||||
|
||||
```console
|
||||
sh screenshots/generate_screenshots.sh
|
||||
sh screenshots/generate_screenshots.sh <deviceName>
|
||||
```
|
||||
|
||||
Screenshots will be stored in the _screenshots/_ folder.
|
||||
### iOS
|
||||
|
||||
Apple requires screenshots a specific list of devices, so we can implement a custom generator to cover all those devices.
|
||||
|
||||
Can be run on Simulator.
|
||||
|
||||
```console
|
||||
sh screenshots/generate_ios_screenshots.sh
|
||||
```
|
||||
|
||||
## List of devices
|
||||
|
||||
### Android
|
||||
|
||||
- Pixel 6
|
||||
|
||||
### iOS
|
||||
|
||||
- iPhone 8 Plus
|
||||
- iPhone 13 Pro
|
||||
- iPhone 13 Pro Max
|
||||
- iPhone 15 Pro
|
||||
- iPhone 15 Pro Max
|
||||
- iPad Pro (12.9-inch) (6th generation)
|
||||
|
|
|
@ -70,37 +70,37 @@ void main() {
|
|||
await tester.pumpApplication();
|
||||
|
||||
await tester.takePhoto();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_reflected');
|
||||
await tester.takeScreenshot(binding, 'light-metering_reflected');
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.toggleIncidentMetering(7.3);
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_incident');
|
||||
await tester.takeScreenshot(binding, 'light-metering_incident');
|
||||
}
|
||||
|
||||
await tester.openAnimatedPicker<IsoValuePicker>();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_iso_picker');
|
||||
await tester.takeScreenshot(binding, 'light-metering_iso_picker');
|
||||
|
||||
await tester.tapCancelButton();
|
||||
await tester.tap(find.byTooltip(S.current.tooltipOpenSettings));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings');
|
||||
await tester.takeScreenshot(binding, 'light-settings');
|
||||
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.meteringScreenLayout);
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings_metering_screen_layout');
|
||||
await tester.takeScreenshot(binding, 'light-settings_metering_screen_layout');
|
||||
|
||||
await tester.tapCancelButton();
|
||||
await tester.tapDescendantTextOf<SettingsScreen>(S.current.equipmentProfiles);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapDescendantTextOf<EquipmentProfilesScreen>(mockEquipmentProfiles.first.name);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}-equipment_profiles');
|
||||
await tester.takeScreenshot(binding, 'light-equipment_profiles');
|
||||
|
||||
await tester.tap(find.byIcon(Icons.iso).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.takeScreenshot(binding, '${lightThemeColor.value}_equipment_profiles_iso_picker');
|
||||
},
|
||||
await tester.takeScreenshot(binding, 'light-equipment_profiles_iso_picker');
|
||||
}
|
||||
);
|
||||
|
||||
/// and the additionally the first one with the dark theme
|
||||
|
@ -111,25 +111,27 @@ void main() {
|
|||
await tester.pumpApplication();
|
||||
|
||||
await tester.takePhoto();
|
||||
await tester.takeScreenshot(binding, '${darkThemeColor.value}_metering_reflected');
|
||||
await tester.takeScreenshot(binding, 'dark-metering_reflected');
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.toggleIncidentMetering(7.3);
|
||||
await tester.takeScreenshot(binding, '${darkThemeColor.value}_metering_incident');
|
||||
await tester.takeScreenshot(binding, 'dark-metering_incident');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final String _platformFolder = Platform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
extension on WidgetTester {
|
||||
Future<void> takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async {
|
||||
if (Platform.isAndroid) {
|
||||
await binding.convertFlutterSurfaceToImage();
|
||||
await pumpAndSettle();
|
||||
}
|
||||
await binding.takeScreenshot(name);
|
||||
await binding.takeScreenshot("$_platformFolder/${const String.fromEnvironment('deviceName')}/$name");
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
|
13
screenshots/scripts/generate_ios_screenshots.sh
Normal file
13
screenshots/scripts/generate_ios_screenshots.sh
Normal file
|
@ -0,0 +1,13 @@
|
|||
devices_array=("iPhone 8 Plus" "iPhone 13 Pro" "iPhone 13 Pro Max" "iPhone 15 Pro" "iPhone 15 Pro Max" "iPad Pro (12.9-inch) (6th generation)")
|
||||
|
||||
open -a Simulator
|
||||
|
||||
for i in "${devices_array[@]}"; do # https://www.baeldung.com/linux/shell-script-iterate-over-string-list#2-understanding--and--special-indices
|
||||
echo "$i"
|
||||
xcrun simctl boot "$i"
|
||||
#uid=$(echo "$(fvm flutter devices)" | sed -n -r "s/$i \(mobile\) • (.*) • .* • .*\(simulator\)/\1/p")
|
||||
#echo $uid
|
||||
sh screenshots/scripts/generate_screenshots.sh "$i"
|
||||
done
|
||||
|
||||
killall 'Simulator'
|
|
@ -1,9 +1,12 @@
|
|||
flutter drive \
|
||||
--dart-define="cameraPreviewAspectRatio=240/320" \
|
||||
deviceName="$1"
|
||||
|
||||
fvm flutter drive \
|
||||
-d "$deviceName" \
|
||||
--dart-define="cameraStubImage=assets/camera_stub_image.jpg" \
|
||||
--dart-define="deviceName=$deviceName" \
|
||||
--driver=test_driver/screenshot_driver.dart \
|
||||
--target=screenshots/generate_screenshots.dart \
|
||||
--profile \
|
||||
--debug \
|
||||
--flavor=dev \
|
||||
--no-dds \
|
||||
--endless-trace-buffer \
|
|
@ -7,7 +7,7 @@ Future<void> main() async {
|
|||
await grantCameraPermission();
|
||||
await integrationDriver(
|
||||
onScreenshot: (name, bytes, [args]) async {
|
||||
final File image = await File('screenshots/$name.png').create(recursive: true);
|
||||
final File image = await File('screenshots/generated/$name.png').create(recursive: true);
|
||||
image.writeAsBytesSync(bytes);
|
||||
return true;
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue