Compare commits
280 commits
v0.1.0-alp
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f820f9fbba | ||
![]() |
d6cd537ffd | ||
![]() |
91f90ae8e8 | ||
![]() |
79f702f7ea | ||
![]() |
f0110e0edf | ||
![]() |
0e45d98060 | ||
![]() |
cf471afdfd | ||
![]() |
d95b811d6c | ||
![]() |
2dee63e78e | ||
![]() |
5856100634 | ||
![]() |
492cbaa7ee | ||
![]() |
461efc7ffd | ||
![]() |
c9bc8bc55d | ||
![]() |
c6c52981ee | ||
![]() |
6ddb779679 | ||
![]() |
38407d6c08 | ||
![]() |
8ecab836a3 | ||
![]() |
2439f7bfff | ||
![]() |
5e8f66d75c | ||
![]() |
75dc9aaf13 | ||
![]() |
e7b0726504 | ||
![]() |
b073391491 | ||
![]() |
e334f0f8da | ||
![]() |
aa29ef28e6 | ||
![]() |
fb58b6cd9f | ||
![]() |
fd97fc7fef | ||
![]() |
38f99afa8c | ||
![]() |
c0e26b8840 | ||
![]() |
1e4aa33b67 | ||
![]() |
de011c5ff3 | ||
![]() |
bfe3dc0ef4 | ||
![]() |
1d22ec5439 | ||
![]() |
30418a9cfd | ||
![]() |
c66381f813 | ||
![]() |
d938be61c4 | ||
![]() |
7639998df2 | ||
![]() |
4e5cbc7759 | ||
![]() |
191959b12b | ||
![]() |
b9c4796a7a | ||
![]() |
1b3d6b2b90 | ||
![]() |
98262fc4ee | ||
![]() |
63ff6cc867 | ||
![]() |
66a38f2969 | ||
![]() |
9100ae3066 | ||
![]() |
006158c731 | ||
![]() |
9d5860a53f | ||
![]() |
e53eca1831 | ||
![]() |
0e09c28150 | ||
![]() |
a0f55c4a95 | ||
![]() |
f0d707b071 | ||
![]() |
1e2cd8b5d2 | ||
![]() |
90bfe7c7b8 | ||
![]() |
7f8ea54c6e | ||
![]() |
36d7e081f1 | ||
![]() |
d4deae57ef | ||
![]() |
881778b313 | ||
![]() |
f62f658be8 | ||
![]() |
8c016e548b | ||
![]() |
5c27f726c5 | ||
![]() |
bc7e6e14d0 | ||
![]() |
ec1f1eeeb4 | ||
![]() |
6dd17961b5 | ||
![]() |
d9ce47e0dd | ||
![]() |
5d7853c22e | ||
![]() |
59977cc9b5 | ||
![]() |
d5f28ee5a4 | ||
![]() |
27d56d1061 | ||
![]() |
2f8bb983d5 | ||
![]() |
2117df2f11 | ||
![]() |
bfd0bfe531 | ||
![]() |
2128e7c2e9 | ||
![]() |
d6c4240646 | ||
![]() |
7787558713 | ||
![]() |
134af8ad28 | ||
![]() |
a1ce17d675 | ||
![]() |
4f4b6cf1eb | ||
![]() |
f965b99e1d | ||
![]() |
fc37016770 | ||
![]() |
9cb1cbaa90 | ||
![]() |
de50b03df9 | ||
![]() |
5fe6c46fd7 | ||
![]() |
7d0c6684d1 | ||
![]() |
d66404e085 | ||
![]() |
85c409fbe8 | ||
![]() |
2b2a5441c7 | ||
![]() |
8f5893c7d2 | ||
![]() |
73d0c32323 | ||
![]() |
a2b4c88256 | ||
![]() |
c80bac23b2 | ||
![]() |
1b4be83dda | ||
![]() |
55b0e52d7f | ||
![]() |
561f849eea | ||
![]() |
e340327e32 | ||
![]() |
19fc039723 | ||
![]() |
6566108994 | ||
![]() |
ddc7ec8c8b | ||
![]() |
434327a7d0 | ||
![]() |
068834bfe5 | ||
![]() |
3bb3f12641 | ||
![]() |
37a3b79f04 | ||
![]() |
d36db97959 | ||
![]() |
a52efcd341 | ||
![]() |
f3b08868be | ||
![]() |
0b51db642c | ||
![]() |
e0320b6704 | ||
![]() |
5b1b0b0540 | ||
![]() |
79105ab4f1 | ||
![]() |
2a3c6b0b09 | ||
![]() |
cc660de0c4 | ||
![]() |
4288be7d57 | ||
![]() |
a5ad391273 | ||
![]() |
9029379697 | ||
![]() |
abbc4c92de | ||
![]() |
0fbf252d9e | ||
![]() |
cc9f162933 | ||
![]() |
1be7c3be48 | ||
![]() |
8415f4e515 | ||
![]() |
cf4373d854 | ||
![]() |
4201d36abb | ||
![]() |
429c0a53a2 | ||
![]() |
f39177919c | ||
![]() |
bf3c8aa7c7 | ||
![]() |
da4fcfc857 | ||
![]() |
4bb080a144 | ||
![]() |
d364de4486 | ||
![]() |
e1c320b804 | ||
![]() |
a9f8223e4a | ||
![]() |
aee527dcca | ||
![]() |
5adcee00dd | ||
![]() |
4917ee8aef | ||
![]() |
6e3588a72e | ||
![]() |
dafbc68288 | ||
![]() |
bdb0442dd1 | ||
![]() |
d41fa6fa84 | ||
![]() |
b9412c7441 | ||
![]() |
d91441bac9 | ||
![]() |
9c11401175 | ||
![]() |
737a9aa2c2 | ||
![]() |
886188bb9e | ||
![]() |
1310b78a54 | ||
![]() |
8a71c8db13 | ||
![]() |
6e1aaf5acf | ||
![]() |
c12cfb1697 | ||
![]() |
50c2460f16 | ||
![]() |
6a9036ce5e | ||
![]() |
40c670ad30 | ||
![]() |
119e079554 | ||
![]() |
b02b50bac3 | ||
![]() |
dd5f551fd2 | ||
![]() |
bb9b023fa7 | ||
![]() |
dbf1f09eb6 | ||
![]() |
b13acedebd | ||
![]() |
47f7b61230 | ||
![]() |
37fe6a4a5d | ||
![]() |
a25ccc0fad | ||
![]() |
b53603b3f5 | ||
![]() |
e001c153fb | ||
![]() |
ed83540dde | ||
![]() |
79d6034894 | ||
![]() |
8ff387c5c5 | ||
![]() |
2735f0b66f | ||
![]() |
0c58134733 | ||
![]() |
a7b8de6912 | ||
![]() |
4d1550f1e7 | ||
![]() |
02ece15c5d | ||
![]() |
74d0a7101c | ||
![]() |
0013125d68 | ||
![]() |
ad8f0d895a | ||
![]() |
1a64ef987b | ||
![]() |
cdf7372913 | ||
![]() |
a0eb641cf3 | ||
![]() |
c63b5a420d | ||
![]() |
ba0d6d3fab | ||
![]() |
8b74c4e1a4 | ||
![]() |
00b2728453 | ||
![]() |
749f2beb47 | ||
![]() |
239e1004a3 | ||
![]() |
ec9ba1a779 | ||
![]() |
5602b1ed80 | ||
![]() |
19a788adcb | ||
![]() |
f68aea5133 | ||
![]() |
4f8a415503 | ||
![]() |
0d3641f618 | ||
![]() |
bd675184de | ||
![]() |
f1f8d74338 | ||
![]() |
1a73cefa76 | ||
![]() |
8ded9cf98a | ||
![]() |
4cabd17eb2 | ||
![]() |
2f478836ed | ||
![]() |
34ea034e67 | ||
![]() |
7a0b563d75 | ||
![]() |
2102213344 | ||
![]() |
0583b07cb9 | ||
![]() |
6232ad555a | ||
![]() |
6944da25fa | ||
![]() |
77cbd11268 | ||
![]() |
d3512faa9b | ||
![]() |
d346e706ff | ||
![]() |
59b71abba0 | ||
![]() |
8a168b1424 | ||
![]() |
0ac24e5d2c | ||
![]() |
b936924452 | ||
![]() |
dc2c33e494 | ||
![]() |
efad098ea5 | ||
![]() |
aaadd1ded6 | ||
![]() |
be0617a99c | ||
![]() |
6bf059ed4d | ||
![]() |
6ffd164171 | ||
![]() |
cd61204e50 | ||
![]() |
6bd104660c | ||
![]() |
81e01c126a | ||
![]() |
0fb4fc1e47 | ||
![]() |
89ad717fe7 | ||
![]() |
57685beeea | ||
![]() |
7b9f9a91c0 | ||
![]() |
59287d06bc | ||
![]() |
c48f2ae373 | ||
![]() |
e6338157bf | ||
![]() |
1c4e302103 | ||
![]() |
b7639740f3 | ||
![]() |
333563b0f0 | ||
![]() |
cbee0f350a | ||
![]() |
f8391454b6 | ||
![]() |
9945005008 | ||
![]() |
7eacc7d03b | ||
![]() |
6a05c42ee8 | ||
![]() |
8d1640447f | ||
![]() |
59e440dfd7 | ||
![]() |
1372a75d3b | ||
![]() |
37c20c5f94 | ||
![]() |
fdd00a2646 | ||
![]() |
0c0e4eeb3b | ||
![]() |
2bee357896 | ||
![]() |
824be6c916 | ||
![]() |
a6d5032c83 | ||
![]() |
871a6dcc6c | ||
![]() |
0a2854e70f | ||
![]() |
af6b3dbedc | ||
![]() |
3ed8048c99 | ||
![]() |
4966c330ec | ||
![]() |
485636c706 | ||
![]() |
afdbc92ac4 | ||
![]() |
e4deac0fee | ||
![]() |
07fd61fa1e | ||
![]() |
a183a5433e | ||
![]() |
f75afbb6c2 | ||
![]() |
186bb8f468 | ||
![]() |
abcdd35511 | ||
![]() |
278e95e5fb | ||
![]() |
8d73a7d2e3 | ||
![]() |
9436a59094 | ||
![]() |
b8c6f51bd0 | ||
![]() |
00e104de9b | ||
![]() |
9cfffc3377 | ||
![]() |
cb4e91cb0a | ||
![]() |
9ffb5112c1 | ||
![]() |
179008ff77 | ||
![]() |
42fe5d45bc | ||
![]() |
130f5ff0b2 | ||
![]() |
31ef42c4c0 | ||
![]() |
ab0b0cefab | ||
![]() |
b66d85deb5 | ||
![]() |
703b29a3d5 | ||
![]() |
9477f80ada | ||
![]() |
9be413752a | ||
![]() |
c7ed4d332e | ||
![]() |
9bedc6e665 | ||
![]() |
cc12d32af0 | ||
![]() |
02a7f7d6a9 | ||
![]() |
cd9ea81dd1 | ||
![]() |
02e5fc582c | ||
![]() |
bdf03f708f | ||
![]() |
5bea96669d | ||
![]() |
5eb0869aa0 | ||
![]() |
56480cca0a | ||
![]() |
cdd714f12c | ||
![]() |
4eee9816bd | ||
![]() |
3ccabbfac4 | ||
![]() |
abd07764fd | ||
![]() |
14bac950cf |
3
.fvmrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"flutter": "3.24.5"
|
||||
}
|
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [vodemn]
|
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help improve the app
|
||||
title: ''
|
||||
labels: bug, user feedback
|
||||
assignees: vodemn
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Device:**
|
||||
- Device: [e.g. Pixel 6]
|
||||
- OS: [e.g. Android 12]
|
||||
|
||||
**App version**
|
17
.github/ISSUE_TEMPLATE/feature-request-or-improvement.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Feature request or improvement
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature, user feedback
|
||||
assignees: vodemn
|
||||
|
||||
---
|
||||
|
||||
**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.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
8
.github/scripts/increment_build_number.sh
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
export newVersion="$1"
|
||||
|
||||
if [[ -n "$newVersion" ]]; then
|
||||
#https://stackoverflow.com/a/30214769/13167574
|
||||
perl -i -pe 's/^(version:\s+)(\d+\.\d+\.\d+)(\+)(\d+)$/$1.$ENV{'newVersion'}.$3.($4+1)/e' pubspec.yaml
|
||||
else
|
||||
echo "argument error"
|
||||
fi
|
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
|
||||
|
||||
base64 -d <<< "$content" > "$filename"
|
2
.github/scripts/stub_iap.sh
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# https://unix.stackexchange.com/questions/435708/regex-multiline-pattern-and-substitution-replacement
|
||||
perl -0777 -i -pe 's/( m3_lightmeter_iap:\n)( git:\n url: "https:\/\/github.com\/vodemn\/m3_lightmeter_iap"\n ref: v\d{1,2}.\d{1,2}.\d{1,2})/$1 path: iap/sg' pubspec.yaml
|
151
.github/workflows/build_apk.yml
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
# 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 Android
|
||||
|
||||
run-name: Build Android (${{ inputs.binary-type == 'apk' && '.apk' || '.aab' }}) v${{ inputs.version }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
required: true
|
||||
type: string
|
||||
binary-type:
|
||||
description: "Binary type"
|
||||
type: string
|
||||
required: true
|
||||
flavor:
|
||||
description: "Flavor"
|
||||
type: string
|
||||
required: true
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
stage-backend:
|
||||
type: boolean
|
||||
description: Use stage backend
|
||||
default: true
|
||||
upload-artifact:
|
||||
type: boolean
|
||||
description: Upload build to artifacts
|
||||
default: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
required: true
|
||||
type: string
|
||||
binary-type:
|
||||
description: "Binary type"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- apk
|
||||
- appbundle
|
||||
flavor:
|
||||
description: "Flavor"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- dev
|
||||
- prod
|
||||
default: dev
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
stage-backend:
|
||||
type: boolean
|
||||
description: Use stage backend
|
||||
default: true
|
||||
upload-artifact:
|
||||
type: boolean
|
||||
description: Upload build to artifacts
|
||||
default: true
|
||||
|
||||
env:
|
||||
BUILD_ARGS: --release --flavor ${{ inputs.flavor }} -t lib/main_${{ inputs.flavor }}.dart
|
||||
BUILD_APK_PATH: build/app/outputs/flutter-apk/app-${{ inputs.flavor }}-release.apk
|
||||
BUILD_AAB_PATH: build/app/outputs/bundle/${{ inputs.flavor }}Release/app-${{ inputs.flavor }}-release.aab
|
||||
RELEASE_NOTES_ARTIFACT_NAME: release_notes_en_${{ inputs.version }}
|
||||
RELEASE_NOTES_PATH: "assets/release_notes"
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
name: Build ${{ inputs.binary-type == 'apk' && '.apk' || '.aab' }}
|
||||
runs-on: macos-latest
|
||||
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: "17"
|
||||
|
||||
- name: Restore Android keystore .jsk and .properties files
|
||||
run: |
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.KEYSTORE_PROPERTIES }}" "android/key.properties"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.KEYSTORE }}" "android/app/keystore.jks"
|
||||
|
||||
- name: Restore google-services.json
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}" "android/app/google-services.json"
|
||||
|
||||
- name: Setup Firebase
|
||||
run: |
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_JSON }}" "firebase.json"
|
||||
|
||||
- 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: Increment build number & replace version number
|
||||
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Download release notes
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
||||
path: ${{ env.RELEASE_NOTES_PATH }}
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.24.5"
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
|
||||
- name: Build ${{ inputs.binary-type }}
|
||||
run: flutter build ${{ inputs.binary-type }} $BUILD_ARGS
|
||||
|
||||
- name: Upload ${{ inputs.binary-type }} to artifacts
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: m3_lightmeter_${{ inputs.binary-type }}
|
||||
path: ${{ inputs.binary-type == 'apk' && env.BUILD_APK_PATH || env.BUILD_AAB_PATH }}
|
156
.github/workflows/build_ipa.yml
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
# 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 v${{ inputs.version }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
required: true
|
||||
type: string
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
stage-backend:
|
||||
type: boolean
|
||||
description: Use stage backend
|
||||
default: true
|
||||
upload-artifact:
|
||||
type: boolean
|
||||
description: Upload build to artifacts
|
||||
default: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
required: true
|
||||
type: string
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
stage-backend:
|
||||
type: boolean
|
||||
description: Use stage backend
|
||||
default: true
|
||||
upload-artifact:
|
||||
type: boolean
|
||||
description: Upload build to artifacts
|
||||
default: true
|
||||
|
||||
env:
|
||||
FLAVOR: "prod"
|
||||
RELEASE_NOTES_ARTIFACT_NAME: release_notes_en_${{ inputs.version }}
|
||||
RELEASE_NOTES_PATH: "assets/release_notes"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build .ipa
|
||||
runs-on: macos-latest
|
||||
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 GoogleService-Info.plist
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist"
|
||||
|
||||
- name: Restore ExportOptions.plist
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.APP_STORE_EXPORT_OPTIONS }}" "ios/Runner/ExportOptions.plist"
|
||||
|
||||
- name: Setup Firebase
|
||||
run: |
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_APP_ID_FILE }}" "ios/firebase_app_id_file.json"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_JSON }}" "firebase.json"
|
||||
|
||||
- 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: Increment build number & replace version number
|
||||
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Download release notes
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
||||
path: ${{ env.RELEASE_NOTES_PATH }}
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.24.5"
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
dart pub global activate flutterfire_cli
|
||||
|
||||
- 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
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: m3_lightmeter_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
|
37
.github/workflows/ci.yml
vendored
|
@ -1,37 +0,0 @@
|
|||
# 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: Dart
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
|
||||
- name: Check flutter version
|
||||
run: flutter --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Generate intl
|
||||
run: flutter pub run intl_utils:generate
|
||||
|
||||
- name: Analyze project source
|
||||
run: flutter analyze
|
||||
|
||||
- name: Run tests
|
||||
run: flutter test
|
240
.github/workflows/create_release.yml
vendored
Normal file
|
@ -0,0 +1,240 @@
|
|||
# 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.
|
||||
|
||||
# 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
|
||||
|
||||
name: Create new release
|
||||
|
||||
run-name: Release v${{ inputs.version }}${{ inputs.release-track == 'beta' && '-Beta' || '' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
required: true
|
||||
type: string
|
||||
release-notes:
|
||||
description: "Release notes"
|
||||
required: true
|
||||
type: string
|
||||
run-integration-tests:
|
||||
description: "Run integration tests"
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
deploy-ios:
|
||||
description: "Publish to App Store"
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
deploy-android:
|
||||
description: "Publish to Google Play"
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
release-track:
|
||||
description: "Release track"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- production
|
||||
- beta
|
||||
default: production
|
||||
|
||||
env:
|
||||
RELEASE_NOTES_ARTIFACT_NAME: release_notes_en_${{ inputs.version }}
|
||||
RELEASE_NOTES_FILE: release_notes_en_${{ inputs.version }}.md
|
||||
RELEASE_NOTES_PATH: "assets/release_notes"
|
||||
|
||||
jobs:
|
||||
run-integration-tests:
|
||||
name: Run integration tests
|
||||
if: ${{ inputs.run-integration-tests }}
|
||||
uses: ./.github/workflows/run_integration_tests.yml
|
||||
secrets: inherit
|
||||
|
||||
generate-release-notes:
|
||||
name: Generate release notes
|
||||
needs: [run-integration-tests]
|
||||
if: ${{ always() && !failure() && !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
echo ${{ inputs.release-notes }} > ${{ env.RELEASE_NOTES_FILE }}
|
||||
perl -i -pe 's/\s{1}(-{1})/\n$1/g' ${{ env.RELEASE_NOTES_FILE }}
|
||||
- name: Upload merged_native_libs.zip to artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
||||
path: ${{ env.RELEASE_NOTES_FILE }}
|
||||
|
||||
build-android:
|
||||
name: Build Android
|
||||
needs: [generate-release-notes]
|
||||
if: ${{ always() && !failure() && !cancelled() && inputs.deploy-android }}
|
||||
strategy:
|
||||
matrix:
|
||||
binary-type: [apk, appbundle]
|
||||
uses: ./.github/workflows/build_apk.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
binary-type: ${{ matrix.binary-type }}
|
||||
flavor: prod
|
||||
upload-artifact: true
|
||||
stage-backend: false
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
build-ios:
|
||||
name: Build iOS
|
||||
needs: [generate-release-notes]
|
||||
if: ${{ always() && !failure() && !cancelled() && inputs.deploy-ios }}
|
||||
uses: ./.github/workflows/build_ipa.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
stage-backend: false
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
create-github-release:
|
||||
name: Create Github release
|
||||
needs: [build-android, build-ios]
|
||||
if: ${{ always() && !cancelled() && inputs.deploy-android}}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download apk
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: m3_lightmeter_apk
|
||||
|
||||
- name: Download release notes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
||||
path: ${{ env.RELEASE_NOTES_PATH }}
|
||||
|
||||
- name: Increment build number & replace version number
|
||||
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add --all -- ":!app-prod-release.apk"
|
||||
git commit -m "Release v${{ inputs.version }}"
|
||||
|
||||
- name: Push to main
|
||||
uses: CasperWA/push-protected@v2
|
||||
with:
|
||||
token: ${{ secrets.PUSH_TO_MAIN_TOKEN }}
|
||||
branch: ${{ github.ref_name }}
|
||||
unprotect_reviews: true
|
||||
|
||||
- name: Rename apk
|
||||
run: mv app-prod-release.apk m3_lightmeter.apk
|
||||
|
||||
- uses: ncipollo/release-action@v1.12.0
|
||||
with:
|
||||
artifacts: "m3_lightmeter.apk"
|
||||
skipIfReleaseExists: true
|
||||
prerelease: ${{ inputs.release-track == 'beta' }}
|
||||
tag: "v${{ github.event.inputs.version }}${{ inputs.release-track == 'beta' && '-beta' || '' }}"
|
||||
bodyFile: "${{ env.RELEASE_NOTES_PATH }}/${{ env.RELEASE_NOTES_FILE }}"
|
||||
|
||||
create-google-play-release:
|
||||
name: Create Google Play release
|
||||
needs: [build-android, build-ios]
|
||||
if: ${{ always() && !failure() && !cancelled() && inputs.deploy-android }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download app bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: m3_lightmeter_appbundle
|
||||
|
||||
- name: Extract & zip merged_native_libs
|
||||
run: |
|
||||
unzip app-prod-release.aab
|
||||
(cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .)
|
||||
|
||||
- name: Download release notes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
||||
|
||||
- name: Move release notes to a folder
|
||||
run: |
|
||||
mv ${{ env.RELEASE_NOTES_FILE }} whatsnew-en-US
|
||||
mkdir whatsnew
|
||||
mv whatsnew-en-US whatsnew
|
||||
|
||||
# https://unix.stackexchange.com/questions/13466/can-grep-output-only-specified-groupings-that-match'
|
||||
# https://stackoverflow.com/questions/74353311/github-workflow-unable-to-process-file-command-env-successfully
|
||||
- name: Create Google Play release name
|
||||
id: release-name
|
||||
run: |
|
||||
RELEASE_NAME=$(echo "$(cat pubspec.yaml)" | sed -n -r "s/^version:\s{1}(.*)[+](.*)$/700\2 (\1)/p")
|
||||
echo "release_name=$RELEASE_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Google Play release
|
||||
id: create-google-play-release-step
|
||||
uses: r0adkll/upload-google-play@v1.1.1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.GH_ACTIONS_SERVICE_ACCOUNT_JSON }}
|
||||
packageName: com.vodemn.lightmeter
|
||||
releaseFiles: app-prod-release.aab
|
||||
releaseName: ${{ env.release_name }}
|
||||
track: ${{ inputs.release-track }}
|
||||
status: completed
|
||||
debugSymbols: merged_native_libs.zip
|
||||
whatsNewDirectory: whatsnew
|
||||
|
||||
upload-to-app-store:
|
||||
name: Upload to App Store
|
||||
needs: [build-android, build-ios]
|
||||
if: ${{ always() && !failure() && !cancelled() && inputs.deploy-ios }}
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download ipa
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: m3_lightmeter_ipa
|
||||
|
||||
- name: Upload app to TestFlight
|
||||
run: xcrun altool --upload-app -f lightmeter.ipa -t ios -u ${{ secrets.APP_STORE_USERNAME }} -p ${{ secrets.APP_STORE_PASSWORD }}
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
if: ${{ always() }}
|
||||
needs:
|
||||
[create-github-release, create-google-play-release, upload-to-app-store]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete release artifacts
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
failOnError: false
|
||||
name: |
|
||||
m3_lightmeter_apk
|
||||
m3_lightmeter_appbundle
|
||||
m3_lightmeter_ipa
|
||||
${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
|
33
.github/workflows/crowdin_push.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: Crowdin push
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- lib/l10n/**
|
||||
branches:
|
||||
- main
|
||||
- crowdin
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: 567473
|
||||
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
|
||||
jobs:
|
||||
upload-files:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main branch
|
||||
uses: actions/checkout@v3
|
||||
- name: Upload
|
||||
uses: crowdin/github-action@1.5.0
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
upload_sources: true
|
||||
upload_sources_args: "--delete-obsolete"
|
||||
upload_translations: true
|
||||
import_eq_suggestions: true
|
||||
auto_approve_imported: false
|
||||
add_crowdin_branch: main
|
120
.github/workflows/pr_check.yml
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
# 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: PR check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
analyze-and-test:
|
||||
name: Analyze & test
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
EMPTY_CONSTANTS: Y29uc3QgU3RyaW5nIGlhcFNlcnZlclVybCA9ICcnOwpjb25zdCBTdHJpbmcgaXNzdWVzUmVwb3J0VXJsID0gJyc7CmNvbnN0IFN0cmluZyBzb3VyY2VDb2RlVXJsID0gJyc7CmNvbnN0IFN0cmluZyBjb250YWN0RW1haWwgPSAnJzsK
|
||||
EMPTY_FIREBASE_OPTIONS: Ly8gaWdub3JlX2Zvcl9maWxlOiB0eXBlPWxpbnQKaW1wb3J0ICdwYWNrYWdlOmZpcmViYXNlX2NvcmUvZmlyZWJhc2VfY29yZS5kYXJ0JyBzaG93IEZpcmViYXNlT3B0aW9uczsKY2xhc3MgRGVmYXVsdEZpcmViYXNlT3B0aW9ucyB7c3RhdGljIEZpcmViYXNlT3B0aW9ucyBnZXQgY3VycmVudFBsYXRmb3JtID0+RmlyZWJhc2VPcHRpb25zKGFwaUtleTogJycsIGFwcElkOiAnJywgbWVzc2FnaW5nU2VuZGVySWQ6ICcnLCBwcm9qZWN0SWQ6ICcnKTt9Cg==
|
||||
steps:
|
||||
- uses: 8BitJonny/gh-get-current-pr@2.2.0
|
||||
id: PR
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
id: fetch-iap
|
||||
if: steps.PR.outputs.number == 'null' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- name: Override iap package with stub
|
||||
id: override-iap
|
||||
if: steps.fetch-iap.conclusion != 'success'
|
||||
run: bash ./.github/scripts/stub_iap.sh
|
||||
|
||||
- name: Restore secrets
|
||||
env:
|
||||
CONSTANTS: ${{ steps.fetch-iap.conclusion == 'success' && secrets.CONSTANTS || env.EMPTY_CONSTANTS }}
|
||||
FIREBASE_OPTIONS: ${{ steps.fetch-iap.conclusion == 'success' && secrets.FIREBASE_OPTIONS || env.EMPTY_FIREBASE_OPTIONS }}
|
||||
run: |
|
||||
bash .github/scripts/restore_from_base64.sh "${{ env.CONSTANTS }}" "lib/constants.dart"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ env.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.24.5"
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
|
||||
- name: Analyze project source
|
||||
run: flutter analyze lib --fatal-infos
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
defaults -currentHost write -g AppleFontSmoothing -int 0
|
||||
flutter test --dart-define cameraStubImage=assets/camera_stub_image.jpg
|
||||
|
||||
- name: Analyze project source with stub
|
||||
if: steps.override-iap.conclusion != 'success'
|
||||
run: |
|
||||
bash ./.github/scripts/stub_iap.sh
|
||||
flutter pub get
|
||||
flutter analyze lib --fatal-infos
|
||||
|
||||
platform-changes:
|
||||
name: Checks for platform changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
android-changed: ${{ steps.platform-changes.outputs.android-changed }}
|
||||
ios-changed: ${{ steps.platform-changes.outputs.ios-changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- id: platform-changes
|
||||
uses: dorny/paths-filter@v2
|
||||
with:
|
||||
filters: |
|
||||
android-changed:
|
||||
- 'android/**'
|
||||
- 'pubspec.yaml'
|
||||
ios-changed:
|
||||
- 'ios/**'
|
||||
- 'pubspec.yaml'
|
||||
|
||||
build-android:
|
||||
name: Build Android
|
||||
needs: platform-changes
|
||||
if: needs.platform-changes.outputs.android-changed == 'true'
|
||||
uses: ./.github/workflows/build_apk.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
binary-type: apk
|
||||
flavor: prod
|
||||
upload-artifact: false
|
||||
stage-backend: false
|
||||
version: "1.0.0"
|
||||
|
||||
build-ios:
|
||||
name: Build iOS
|
||||
needs: platform-changes
|
||||
if: needs.platform-changes.outputs.ios-changed == 'true'
|
||||
uses: ./.github/workflows/build_ipa.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: false
|
||||
stage-backend: false
|
||||
version: "1.0.0"
|
58
.github/workflows/run_integration_tests.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
# 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: Run integration tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
run-integration-tests:
|
||||
name: Run integration tests
|
||||
timeout-minutes: 90
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Override iap package with stub
|
||||
run: bash ./.github/scripts/stub_iap.sh
|
||||
|
||||
- name: Restore secrets
|
||||
run: |
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_APP_ID_FILE }}" "ios/firebase_app_id_file.json"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_JSON }}" "firebase.json"
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.24.5"
|
||||
|
||||
- name: Prepare app
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
flutter analyze lib --fatal-infos
|
||||
dart pub global activate flutterfire_cli
|
||||
|
||||
- name: Launch iOS simulator
|
||||
uses: futureware-tech/simulator-action@v3
|
||||
with:
|
||||
model: "iPhone 15 Pro"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
flutter drive \
|
||||
--target=integration_test/run_all_tests.dart \
|
||||
--driver=test_driver/integration_driver.dart \
|
||||
--flavor=dev \
|
||||
--no-dds \
|
||||
--dart-define cameraStubImage=assets/camera_stub_image.jpg
|
21
.gitignore
vendored
|
@ -5,9 +5,11 @@
|
|||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
|
@ -48,5 +50,20 @@ app.*.map.json
|
|||
/ios/build/
|
||||
/ios/Runner.xcodeproj/project.pbxproj
|
||||
|
||||
pubspec.lock
|
||||
/ios/Podfile.lock
|
||||
/ios/Podfile.lock
|
||||
|
||||
.fvm/
|
||||
.jks
|
||||
keystore.properties
|
||||
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
|
||||
**/failures/*.png
|
||||
screenshots/generated/raw/
|
||||
|
||||
firebase.json
|
52
.vscode/launch.json
vendored
|
@ -5,34 +5,74 @@
|
|||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "dev",
|
||||
"name": "dev-debug",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev (mock)",
|
||||
"name": "dev-profile",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_mock.dart",
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "prod",
|
||||
"name": "prod-debug",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"prod",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
},
|
||||
{
|
||||
"name": "prod-profile",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"prod",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev-simulator",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraStubImage=assets/camera_stub_image.jpg"
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "integration-test",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraStubImage=assets/camera_stub_image.jpg"
|
||||
],
|
||||
"program": "${workspaceFolder}/integration_test/run_all_tests.dart",
|
||||
},
|
||||
],
|
||||
}
|
25
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"task.slowProviderWarning": true,
|
||||
"dart.flutterSdkPath": ".fvm/flutter_sdk",
|
||||
"search.exclude": {
|
||||
"**/.fvm": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.fvm": true
|
||||
},
|
||||
"dart.lineLength": 120,
|
||||
"[dart]": {
|
||||
"editor.rulers": [
|
||||
120
|
||||
],
|
||||
"editor.selectionHighlight": true,
|
||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||
"editor.suggestSelection": "first",
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.wordBasedSuggestions": "off"
|
||||
},
|
||||
"dart.doNotFormat": [
|
||||
"**/generated/**",
|
||||
"lib/data/**"
|
||||
]
|
||||
}
|
47
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "fvm_build_android_dev",
|
||||
"type": "shell",
|
||||
"command": ".fvm/flutter_sdk/bin/flutter",
|
||||
"args": [
|
||||
"build",
|
||||
"apk",
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--release",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "fvm_build_android_prod",
|
||||
"type": "shell",
|
||||
"command": ".fvm/flutter_sdk/bin/flutter",
|
||||
"args": [
|
||||
"build",
|
||||
"apk",
|
||||
"--flavor",
|
||||
"prod",
|
||||
"--release",
|
||||
"-t",
|
||||
"lib/main_prod.dart",
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "fvm_build_appbundle",
|
||||
"type": "shell",
|
||||
"command": ".fvm/flutter_sdk/bin/flutter",
|
||||
"args": [
|
||||
"build",
|
||||
"appbundle",
|
||||
"--flavor",
|
||||
"prod",
|
||||
"--release",
|
||||
"-t",
|
||||
"lib/main_prod.dart",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
27
PRIVACY_POLICY.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
**Privacy Policy**
|
||||
|
||||
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**
|
||||
|
||||
When using this app no personal data is collected. All settings such as theme, calibration values and other preferences are stored only on your device and never sent over the Internet.
|
||||
|
||||
**Log Data**
|
||||
|
||||
I want to inform you that whenever you use Lightmeter app, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device name, operating system version, the configuration of the app, the time and date of your use of the App, and other statistics.
|
||||
|
||||
**Links to Other Sites**
|
||||
|
||||
This app contains links to other sites. If you click on a third-party link, you will be directed to that site. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
|
||||
|
||||
* [GitHub](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement)
|
||||
|
||||
**Changes to This Privacy Policy**
|
||||
|
||||
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 2024-01-04
|
||||
|
||||
**Contact Us**
|
||||
|
||||
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at contact.vodemn@gmail.com.
|
84
README.md
|
@ -1 +1,83 @@
|
|||
# material_lightmeter
|
||||
<img src="resources/social_preview.png" width="100%" />
|
||||
|
||||

|
||||

|
||||
|
||||
# Table of contents
|
||||
|
||||
- [Table of contents](#table-of-contents)
|
||||
- [Backstory](#backstory)
|
||||
- [Screenshots](#screenshots)
|
||||
- [Development](#development)
|
||||
- [Support](#support)
|
||||
- [iOS Limitations](#ios-limitations)
|
||||
|
||||
# Backstory
|
||||
|
||||
Some time ago I've started developing the [Material Lightmeter](https://play.google.com/store/apps/details?id=com.vodemn.lightmeter&hl=en&gl=US) app. Unfortunately, the last update of this app was almost a year prior to creation of this repo. So after reading some positive review on Google Play saying that "this is an excellent app, too bad it is no longer updated", I've decided to make an update and also make this app open source. Maybe someone sometime will decide to contribute to this project.
|
||||
|
||||
But as the existing repo contained some sensitive data, that I've pushed due to lack of experience, I had to make a new one. And if creating a new repo, why not rewrite the app from scratch?
|
||||
|
||||
Without further delay behold my new Lightmeter app inspired by Material You (a.k.a. M3)
|
||||
|
||||
# Screenshots
|
||||
|
||||
<p float="center">
|
||||
<img src="screenshots/generated/ios/iphone65inch/light_metering-reflected.png" width="18.8%" />
|
||||
<img src="screenshots/generated/ios/iphone65inch/light_equipment-profiles.png" width="18.8%" />
|
||||
<img src="screenshots/generated/ios/iphone65inch/light_settings.png" width="18.8%" />
|
||||
<img src="screenshots/generated/ios/iphone65inch/light_timer.png" width="18.8%" />
|
||||
<img src="screenshots/generated/ios/iphone65inch/dark_metering-reflected.png" width="18.8%" />
|
||||
</p>
|
||||
|
||||
# Development
|
||||
|
||||
### 1. Install Flutter
|
||||
|
||||
To build this app you need to install Flutter 3.24.5 stable. [How to install](https://docs.flutter.dev/get-started/install).
|
||||
|
||||
### 2. Project setup
|
||||
|
||||
#### Restore git-ignored files:
|
||||
|
||||
For macOS you can just run the following script:
|
||||
|
||||
```console
|
||||
sh scripts/setup_fork.sh
|
||||
```
|
||||
|
||||
Or create the files manually using the contents from the script.
|
||||
|
||||
#### Get dependencies
|
||||
|
||||
> If you are using VSCode, you can open the workspace like so: _File -> Open Workspace from File -> m3_lightmeter.code-workspace_. Otherwise you have to run `flutter pub get` command from the iap folder.
|
||||
|
||||
Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:
|
||||
|
||||
```console
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
```
|
||||
|
||||
### 3. Build
|
||||
|
||||
- Checkout [Build .apk](.github/workflows/build_apk.yml) workflow for Android
|
||||
- Checkout [Build .ipa](.github/workflows/build_ipa.yml) workflow for iOS
|
||||
|
||||
# Support
|
||||
|
||||
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).
|
||||
|
||||
In case you have any other questions please contact me via [email](mailto:contact.vodemn@gmail.com?subject="Lightmeter").
|
||||
|
||||
# iOS Limitations
|
||||
|
||||
A list of features, that Android version of the app has and that iOS does not.
|
||||
|
||||
## Incident light metering
|
||||
|
||||
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
|
||||
|
||||
## Volume buttons action
|
||||
|
||||
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
include: package:lint/strict.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
use_setters_to_change_properties: false
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "**/generated/**"
|
||||
- lib/firebase_options.dart
|
|
@ -1,3 +1,11 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
id "com.google.gms.google-services"
|
||||
id "com.google.firebase.crashlytics"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
|
@ -6,11 +14,6 @@ if (localPropertiesFile.exists()) {
|
|||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
|
@ -21,13 +24,15 @@ if (flutterVersionName == null) {
|
|||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
ndkVersion flutter.ndkVersion
|
||||
compileSdkVersion 35
|
||||
ndkVersion "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -35,7 +40,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
@ -43,25 +48,57 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 34
|
||||
ndk {
|
||||
debugSymbolLevel 'FULL'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
/// legacy material-lightmeter ap stopped updating after 60022
|
||||
/// 7xxxx means that it is a new app
|
||||
versionCode 70000 + flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "app"
|
||||
productFlavors {
|
||||
dev {
|
||||
applicationId "com.vodemn.lightmeter.dev"
|
||||
resValue "string", "app_name", "Lightmeter (DEV)"
|
||||
dimension "app"
|
||||
signingConfig signingConfigs.debug
|
||||
signingConfig signingConfigs.release
|
||||
applicationIdSuffix ".dev"
|
||||
}
|
||||
prod {
|
||||
applicationId "com.vodemn.lightmeter"
|
||||
resValue "string", "app_name", "Lightmeter"
|
||||
dimension "app"
|
||||
signingConfig signingConfigs.debug
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
ndk {
|
||||
debugSymbolLevel 'FULL'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace 'com.vodemn.lightmeter'
|
||||
}
|
||||
|
||||
flutter {
|
||||
|
@ -69,5 +106,6 @@ flutter {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "com.android.billingclient:billing-ktx:6.0.0"
|
||||
implementation "com.google.firebase:firebase-analytics:17.4.1"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.vodemn.lightmeter">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
|
BIN
android/app/src/dev/ic_launcher-playstore.png
Normal file
After ![]() (image error) Size: 24 KiB |
|
@ -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>
|
|
@ -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>
|
BIN
android/app/src/dev/res/mipmap-hdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 2 KiB |
BIN
android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 2.8 KiB |
BIN
android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 3.7 KiB |
BIN
android/app/src/dev/res/mipmap-mdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 1.3 KiB |
BIN
android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 1.7 KiB |
BIN
android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 2.1 KiB |
BIN
android/app/src/dev/res/mipmap-xhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 2.8 KiB |
BIN
android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 4.2 KiB |
BIN
android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 5.4 KiB |
BIN
android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 4.4 KiB |
BIN
android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 7.2 KiB |
BIN
android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 8.6 KiB |
BIN
android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 6.2 KiB |
After ![]() (image error) Size: 11 KiB |
BIN
android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 12 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#212121</color>
|
||||
</resources>
|
|
@ -1,9 +1,11 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.vodemn.lightmeter">
|
||||
<application
|
||||
android:label="Lightmeter"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -12,18 +14,19 @@
|
|||
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
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
|
@ -33,6 +36,18 @@
|
|||
android:value="2" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:scheme="mailto" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||
</manifest>
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.MICROPHONE" tools:node="remove" />
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
||||
</manifest>
|
BIN
android/app/src/main/ic_launcher-playstore.png
Normal file
After ![]() (image error) Size: 18 KiB |
|
@ -1,6 +1,95 @@
|
|||
package com.vodemn.lightmeter
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.WindowCompat
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.EventChannel.EventSink
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
class MainActivity : FlutterActivity() {
|
||||
private lateinit var keepScreenOnChannel: MethodChannel
|
||||
private lateinit var volumeHandlingChannel: MethodChannel
|
||||
private lateinit var volumeEventChannel: EventChannel
|
||||
private var volumeEventsEmitter: EventSink? = null
|
||||
private var handleVolume = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
keepScreenOnChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
"com.vodemn.lightmeter/keepScreenOn"
|
||||
)
|
||||
keepScreenOnChannel.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"isKeepScreenOn" -> result.success((window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0)
|
||||
"setKeepScreenOn" -> {
|
||||
if (call.arguments !is Boolean) {
|
||||
result.error("invalid args", "Argument should be of type Bool for 'setKeepScreenOn' call", null)
|
||||
} else {
|
||||
if (call.arguments as Boolean) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
result.success(true)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
volumeHandlingChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
"com.vodemn.lightmeter/volumeHandling"
|
||||
)
|
||||
volumeHandlingChannel.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"setVolumeHandling" -> {
|
||||
handleVolume = call.arguments as Boolean
|
||||
result.success(handleVolume)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
volumeEventChannel = EventChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
"com.vodemn.lightmeter/volumeEvents"
|
||||
)
|
||||
volumeEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(listener: Any?, eventSink: EventSink) {
|
||||
volumeEventsEmitter = eventSink
|
||||
}
|
||||
override fun onCancel(listener: Any?) {
|
||||
volumeEventsEmitter = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
keepScreenOnChannel.setMethodCallHandler(null)
|
||||
volumeHandlingChannel.setMethodCallHandler(null)
|
||||
volumeEventChannel.setStreamHandler(null)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onKeyDown(code: Int, event: KeyEvent): Boolean {
|
||||
return when (val keyCode: Int = event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (handleVolume) {
|
||||
volumeEventsEmitter?.success(keyCode)
|
||||
true
|
||||
} else {
|
||||
super.onKeyDown(code, event)
|
||||
}
|
||||
}
|
||||
else -> super.onKeyDown(code, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
After ![]() (image error) Size: 4.4 KiB |
After ![]() (image error) Size: 3.3 KiB |
After ![]() (image error) Size: 11 KiB |
After ![]() (image error) Size: 20 KiB |
After ![]() (image error) Size: 27 KiB |
Before ![]() (image error) Size: 544 B |
Before ![]() (image error) Size: 2.4 KiB |
Before ![]() (image error) Size: 442 B |
Before ![]() (image error) Size: 1.4 KiB |
Before ![]() (image error) Size: 721 B |
Before ![]() (image error) Size: 2.6 KiB |
Before ![]() (image error) Size: 1 KiB |
Before ![]() (image error) Size: 4.1 KiB |
Before ![]() (image error) Size: 1.4 KiB |
Before ![]() (image error) Size: 4.4 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#212121</color>
|
||||
</resources>
|
11
android/app/src/main/res/xml/data_extraction_rules.xml
Normal 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>
|
BIN
android/app/src/prod/ic_launcher-playstore.png
Normal file
After ![]() (image error) Size: 15 KiB |
|
@ -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>
|
|
@ -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>
|
BIN
android/app/src/prod/res/mipmap-hdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 1.4 KiB |
BIN
android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 1.8 KiB |
BIN
android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 3 KiB |
BIN
android/app/src/prod/res/mipmap-mdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 787 B |
BIN
android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 1.1 KiB |
BIN
android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 1.6 KiB |
BIN
android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 2 KiB |
BIN
android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 2.8 KiB |
BIN
android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 4.3 KiB |
BIN
android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 3.1 KiB |
After ![]() (image error) Size: 4.7 KiB |
BIN
android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 6.8 KiB |
BIN
android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 4.4 KiB |
After ![]() (image error) Size: 7.1 KiB |
BIN
android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 10 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#212121</color>
|
||||
</resources>
|
|
@ -1,5 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.vodemn.lightmeter">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
|
|
@ -1,16 +1,3 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
@ -26,6 +13,6 @@ subprojects {
|
|||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx2048M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
include ':app'
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.7.3' apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.21" apply false
|
||||
id "com.google.gms.google-services" version "4.4.0" apply false
|
||||
id "com.google.firebase.crashlytics" version "2.9.9" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
11
assets/README.md
Normal 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).
|
BIN
assets/camera_stub_image.jpg
Normal file
After ![]() (image error) Size: 899 KiB |
Before ![]() (image error) Size: 3.9 KiB |
BIN
assets/launcher_icon_dev_1024.png
Normal file
After ![]() (image error) Size: 21 KiB |
BIN
assets/launcher_icon_dev_512.png
Normal file
After ![]() (image error) Size: 14 KiB |
BIN
assets/launcher_icon_prod_1024.png
Executable file
After ![]() (image error) Size: 14 KiB |
BIN
assets/launcher_icon_prod_512.png
Normal file
After ![]() (image error) Size: 6 KiB |
Before ![]() (image error) Size: 2.2 KiB |
4
assets/release_notes/release_notes_en_0.19.0.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
- [Pro] Added a timer for shooting long exposures without leaving the app, just tap the necessary shutter speed in the list.
|
||||
- Long exposure values are no longer limited by 1" and are generated to match the list of aperture values.
|
||||
- Refined the app's color palette & unified icons across the app.
|
||||
- Added release notes dialog.
|
3
assets/release_notes/release_notes_en_0.20.0.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
- Added ISO values up to 25600.
|
||||
- Improved Pro features description.
|
||||
- Made "Restore purchases" option more accessible.
|
4
assets/release_notes/release_notes_en_0.21.0.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
- Added shutter speed values up to 1/4000.
|
||||
- Added auto scaling for long values in the exposure pairs list.
|
||||
- Fixed false purchasable status when offline.
|
||||
- Fixed a delay when fetching product status on startup.
|
2
assets/release_notes/release_notes_en_1.0.0.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
- [Pro] Added the ability to create custom films.
|
||||
- [Pro] Added the ability to select equipment profiles in use.
|
1
assets/release_notes/release_notes_en_1.0.1.md
Normal file
|
@ -0,0 +1 @@
|
|||
- Performance improvements and bug fixes.
|
1
assets/release_notes/release_notes_en_1.0.2.md
Normal file
|
@ -0,0 +1 @@
|
|||
- Cleaned up camera preview implementation.
|