Compare commits
350 Commits
personalDi
...
sbp_paymen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eee912f05b | ||
|
|
e316bf1e0e | ||
|
|
de19af14bf | ||
|
|
b064c7016e | ||
|
|
e94681266a | ||
|
|
0ca2d1a1cc | ||
|
|
be924265a1 | ||
| a0edfe942b | |||
|
|
51397003b5 | ||
|
|
e10610d93c | ||
|
|
d21cc46558 | ||
|
|
2ab26bbda5 | ||
|
|
41e556d172 | ||
|
|
6d920857f6 | ||
|
|
7183fc5d7d | ||
|
|
3852a06ab4 | ||
| 7db9af9894 | |||
| fb99a42115 | |||
|
|
54c27c28cd | ||
|
|
fe243965e3 | ||
|
|
56313a4fcc | ||
|
|
cb36ef8e07 | ||
|
|
b2072ab626 | ||
| 5b72684c95 | |||
|
|
91ad51d340 | ||
| 01282a35cb | |||
| 5d86b7c71b | |||
|
|
50aefde842 | ||
| 0991ac555e | |||
| 5fbd578463 | |||
|
|
426bd49aa1 | ||
| c6fc20d217 | |||
|
|
17e18dc68b | ||
| b5a889cbb0 | |||
| d483295aab | |||
|
|
c8284338aa | ||
|
|
03ec91a595 | ||
|
|
e814683984 | ||
|
|
684415ef5b | ||
|
|
631ceb2b7a | ||
|
|
71a405c63f | ||
|
|
4ca90eb2df | ||
|
|
f17f1ecb57 | ||
|
|
a71bcc752d | ||
|
|
aac085def4 | ||
| 985154c863 | |||
|
|
b6b13b1db5 | ||
|
|
6629286bf1 | ||
|
|
966bc23f0b | ||
|
|
a13b623ca2 | ||
|
|
ee2dd75a94 | ||
|
|
c450a41715 | ||
|
|
55b6b75134 | ||
|
|
335e36dd18 | ||
| 7106bde4ae | |||
|
|
4f241f7db6 | ||
|
|
3d7755a9a2 | ||
|
|
decc8c63e6 | ||
|
|
ad5b48ef1b | ||
|
|
e8dc0138e2 | ||
|
|
96896671b6 | ||
|
|
5e2f0430d9 | ||
|
|
2da3b10e63 | ||
|
|
5cb121d733 | ||
| f1e07b3d86 | |||
|
|
109013c25c | ||
|
|
f5d14d213f | ||
|
|
26ae28a705 | ||
|
|
7672477c03 | ||
|
|
07277d6e48 | ||
|
|
9d1bedff26 | ||
| 062079e7f1 | |||
|
|
e39638be7f | ||
| 442fc0ec14 | |||
| a8f8cc8eb0 | |||
|
|
3892bf00aa | ||
|
|
a492c2d964 | ||
|
|
89268e1132 | ||
| 60753a13d2 | |||
|
|
08eeea6ba4 | ||
|
|
58d99424a0 | ||
|
|
c6cdb5bf01 | ||
|
|
50deaeddd1 | ||
|
|
56fb2f1bac | ||
|
|
24185cc875 | ||
|
|
9e7bf258d0 | ||
|
|
098633b3ac | ||
|
|
c3aa77b02d | ||
|
|
2d5384f2ec | ||
|
|
1c7e494f90 | ||
|
|
8bbe2613d2 | ||
|
|
5f6ceab6a1 | ||
|
|
250a3f5d73 | ||
|
|
3e0d8b6c27 | ||
|
|
9ed3b37272 | ||
|
|
ae628a6ecc | ||
|
|
88d8ec52d9 | ||
|
|
937921e26f | ||
|
|
d324bd6540 | ||
|
|
5766e216c8 | ||
|
|
ee9b97b694 | ||
|
|
31eec6c9d7 | ||
|
|
5fae3eb5e8 | ||
|
|
b0a167b9e9 | ||
|
|
0b2e026d1d | ||
|
|
0641513ebe | ||
|
|
be0a99b164 | ||
|
|
8b1d08b2aa | ||
|
|
6dab0d159b | ||
| 24652884ee | |||
|
|
5167766dbf | ||
| cc79e40df9 | |||
|
|
cdc5936d3c | ||
|
|
67e770f5d0 | ||
|
|
6f666a8859 | ||
|
|
c540cbe6df | ||
|
|
6c4104e9ea | ||
|
|
6d1bcb85fb | ||
|
|
9753644570 | ||
|
|
75983818d1 | ||
|
|
5b149e2fd2 | ||
|
|
699148b202 | ||
|
|
6e7894b911 | ||
|
|
d4f41fe6cf | ||
|
|
bb97c395dd | ||
| ea9cdb93d4 | |||
|
|
b7804db49d | ||
|
|
e90a466a5b | ||
|
|
5f30aaeb2a | ||
|
|
516dfa4158 | ||
|
|
ae07851396 | ||
|
|
34eccb7db5 | ||
|
|
042cc0ea20 | ||
|
|
f81aebe9d8 | ||
|
|
d70aaf955f | ||
|
|
c91e83adaf | ||
|
|
531f732b1a | ||
|
|
d10f2f1356 | ||
| 7c4010b275 | |||
|
|
bf110b70c1 | ||
|
|
97418e11d4 | ||
|
|
225077907f | ||
|
|
296adc8229 | ||
|
|
7dd7e3b488 | ||
|
|
a496edaf4c | ||
|
|
400cc004ef | ||
|
|
beda152df0 | ||
|
|
42ffde820d | ||
| 4458aaf74f | |||
|
|
adb7b0bd74 | ||
|
|
a768eefed0 | ||
|
|
0ac50d6d18 | ||
|
|
a0d8fda50d | ||
|
|
1d286fa8d9 | ||
|
|
c2a5e7305e | ||
|
|
bbb1b28692 | ||
|
|
21a65caea9 | ||
|
|
eac366e9ae | ||
|
|
278f4d7e10 | ||
|
|
ac5504a514 | ||
|
|
843e1ce4dd | ||
|
|
db6db89567 | ||
|
|
044d86e710 | ||
|
|
4ff880e079 | ||
|
|
05b9ca17c7 | ||
| f20f871c88 | |||
|
|
a1c0c2694b | ||
|
|
2d7407ce99 | ||
| 62fe5ebfa0 | |||
|
|
3dc36ab52d | ||
| 190e212eb4 | |||
|
|
cf3d1a945c | ||
|
|
c54915f45d | ||
|
|
b188570882 | ||
|
|
d0794670f6 | ||
|
|
3b53ed6f7d | ||
|
|
a003814265 | ||
|
|
e99465a550 | ||
|
|
a01f3914c4 | ||
|
|
15f9149cfd | ||
|
|
79df8c5f9a | ||
|
|
a93da1b505 | ||
|
|
8144013747 | ||
|
|
7bac2a13ac | ||
|
|
29589ef6fd | ||
|
|
97a6fe57e9 | ||
|
|
8309498d9e | ||
|
|
aa098b31bc | ||
|
|
7a85c65918 | ||
|
|
6ff35fd162 | ||
|
|
6811b325be | ||
|
|
92205c09d6 | ||
| 008dba9fed | |||
| f61fb8c6b3 | |||
|
|
55ca558bfb | ||
|
|
91499ae207 | ||
|
|
4bacdf5e76 | ||
|
|
e76c1aed20 | ||
|
|
de8f879f59 | ||
|
|
f6809a06b3 | ||
|
|
775d4fb80f | ||
|
|
64e9df69f7 | ||
| 3d14dee4e9 | |||
|
|
6ccfcdedda | ||
|
|
d3c8034b8f | ||
|
|
44ede73ceb | ||
| 2c63990afc | |||
| 38647b4ac6 | |||
|
|
eb69b253bc | ||
|
|
f26886a7c5 | ||
|
|
13ccf56df0 | ||
|
|
c0032f7a95 | ||
|
|
4852d2cf10 | ||
|
|
41e96c63c6 | ||
|
|
c786821275 | ||
|
|
7b425e56f1 | ||
|
|
bf2ba1fbda | ||
| c836b600c6 | |||
|
|
d206e7ec12 | ||
|
|
a778257215 | ||
|
|
497ff19cfa | ||
|
|
bb59552e3e | ||
|
|
55b2bb0436 | ||
|
|
90746d0be7 | ||
|
|
d70d172c69 | ||
|
|
4e23992d22 | ||
|
|
7a846ed218 | ||
|
|
fcc2936c22 | ||
|
|
b11cf2c0b0 | ||
|
|
0ead14bca5 | ||
|
|
08e56d9777 | ||
|
|
99659f7ea1 | ||
|
|
e158d812e4 | ||
|
|
3d774267ca | ||
|
|
aeb89a9aba | ||
|
|
a4f5bfdb4d | ||
|
|
5b705dfb72 | ||
|
|
a792dcd163 | ||
|
|
9c7ca14017 | ||
|
|
38118055a5 | ||
|
|
5d6e25858b | ||
|
|
42b443c3ba | ||
|
|
21f7c40cde | ||
|
|
f56092d88f | ||
|
|
2b97c0f57d | ||
|
|
a1304e4612 | ||
|
|
7b534cf891 | ||
|
|
22b2e5f249 | ||
|
|
5dfdbe4543 | ||
|
|
5c9a9c22ba | ||
|
|
4f920359c8 | ||
|
|
2ab3bc1cf0 | ||
|
|
91bd8f9d68 | ||
| 9c6f6bdfb1 | |||
|
|
fe1e81e455 | ||
|
|
b7e064a560 | ||
|
|
f1b7a24044 | ||
| dfe11a6df2 | |||
|
|
cb441b4027 | ||
| 7282fea59a | |||
|
|
4adec4dbbc | ||
|
|
d5b0e972a5 | ||
|
|
391efeb8dd | ||
| d115af6f8e | |||
|
|
2408443a26 | ||
|
|
c378c7fc16 | ||
|
|
070a3e39e7 | ||
|
|
d1753e99f8 | ||
|
|
960afbd6ed | ||
|
|
70e4a42e98 | ||
|
|
04061cb4c1 | ||
|
|
edd064d20d | ||
|
|
cdb7378265 | ||
|
|
746d366c18 | ||
|
|
c2a506cdbf | ||
|
|
abe4985f0e | ||
|
|
ca06b2bd0f | ||
|
|
04c932646d | ||
|
|
3d4363185b | ||
|
|
953230b387 | ||
|
|
9d845e1264 | ||
|
|
32de60742e | ||
|
|
3923b61c67 | ||
|
|
0cd8e662d9 | ||
| 89ea73fc59 | |||
|
|
9e33063c30 | ||
|
|
f3d4478c65 | ||
| 673aa86370 | |||
|
|
1738d27b82 | ||
|
|
d0becb9e11 | ||
|
|
965ae2f02b | ||
|
|
9fcfac87d4 | ||
|
|
2a25be5aa4 | ||
| b420d11996 | |||
|
|
cbeb91a9f0 | ||
|
|
9ca09e8137 | ||
|
|
8917f54e7b | ||
|
|
626f74d20d | ||
|
|
7df32dabd2 | ||
|
|
2e57cd9519 | ||
|
|
c6c42b0525 | ||
|
|
58bb766721 | ||
|
|
0be7392a39 | ||
| 35cad4e05e | |||
|
|
4deda92c5e | ||
|
|
b83d91e7f2 | ||
| e5029f581d | |||
|
|
c69fe7f0a6 | ||
| fc0e9b1063 | |||
|
|
453f119317 | ||
|
|
6d0bee4da8 | ||
|
|
04d4be38d4 | ||
| fa22cf5d36 | |||
| 91e8348015 | |||
|
|
1b948b99e3 | ||
|
|
b6eac3c46f | ||
|
|
6fa944b3df | ||
|
|
c31411c4a2 | ||
|
|
6214d19a14 | ||
|
|
db5dddff86 | ||
|
|
39775cd7a8 | ||
|
|
36e2f12e3f | ||
|
|
66d2533add | ||
|
|
425f16bfc9 | ||
|
|
c5e30d8f00 | ||
|
|
d0935fbaae | ||
| dda0aa7440 | |||
|
|
c81327b895 | ||
| 732d667ac1 | |||
|
|
ba9f4a3584 | ||
|
|
f63d901a55 | ||
|
|
9148a4c668 | ||
|
|
a3f0aa3e67 | ||
|
|
8a390c2a9b | ||
|
|
92b37e77b1 | ||
| 101c1e15af | |||
|
|
172c05ac69 | ||
|
|
4eecc458f8 | ||
|
|
4b781adb79 | ||
|
|
dc400ef5b7 | ||
|
|
d1160ba35f | ||
|
|
a46ac5bb4c | ||
|
|
992cb38cdd | ||
|
|
2a06a5476a | ||
|
|
f3853b58d9 | ||
|
|
bbc99ec9f4 | ||
| ff946a5938 | |||
|
|
12f51d49ea | ||
|
|
e147f6e182 | ||
|
|
2440204294 |
@@ -3,19 +3,19 @@ name: Eatvio App Android Test
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.32.0"
|
flutter_version: "3.41.2"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: [ dev-office ]
|
runs-on: [ ubuntu-latest ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: https://github.com/actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: ${{ env.java_version }}
|
java-version: ${{ env.java_version }}
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Install Android SDK
|
- name: Install Android SDK
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y openjdk-11-jdk wget
|
apt-get install -y openjdk-11-jdk wget xz-utils tar
|
||||||
wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip
|
wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip
|
||||||
unzip commandlinetools.zip
|
unzip commandlinetools.zip
|
||||||
mkdir -p $HOME/Android/Sdk/cmdline-tools/latest
|
mkdir -p $HOME/Android/Sdk/cmdline-tools/latest
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- id: flutter-action
|
- id: flutter-action
|
||||||
uses: actions/flutter-action@v2
|
uses: https://github.com/subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: ${{ env.flutter_channel }}
|
channel: ${{ env.flutter_channel }}
|
||||||
flutter-version: ${{ env.flutter_version }}
|
flutter-version: ${{ env.flutter_version }}
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK artifact
|
- name: Upload APK artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: https://github.com/actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_NAME }}
|
path: ${{ env.APK_NAME }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Eatvio App Android Test on custom docker
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.32.0"
|
flutter_version: "3.41.2"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: [ dev-office-flutter ]
|
runs-on: [ dev-office-flutter ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Android environment variables
|
- name: Set up Android environment variables
|
||||||
id: "android-env-setup"
|
id: "android-env-setup"
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
# echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
# echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
# - name: Upload APK artifact
|
# - name: Upload APK artifact
|
||||||
# uses: actions/upload-artifact@v3
|
# uses: https://github.com/actions/upload-artifact@v3
|
||||||
# with:
|
# with:
|
||||||
# name: ${{ env.APK_NAME }}
|
# name: ${{ env.APK_NAME }}
|
||||||
# path: ${{ env.APK_NAME }}
|
# path: ${{ env.APK_NAME }}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: [macos-arm64]
|
runs-on: [macos-arm64]
|
||||||
steps:
|
steps:
|
||||||
|
- name: Fastlane version
|
||||||
|
run: fastlane --version
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: https://github.com/actions/checkout@v2
|
||||||
|
|
||||||
- name: Checkout env repository
|
- name: Checkout env repository
|
||||||
uses: actions/checkout@v2
|
uses: https://github.com/actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: mso7nueh/eatvio_app_env
|
repository: mso7nueh/eatvio_app_env
|
||||||
path: env_repo
|
path: env_repo
|
||||||
@@ -54,11 +57,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Flutter build
|
- name: Flutter build
|
||||||
run: |
|
run: |
|
||||||
|
flutter upgrade
|
||||||
flutter clean
|
flutter clean
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter pub upgrade
|
flutter pub upgrade
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
# Configure Flutter for release mode
|
# Configure Flutter for release mode
|
||||||
|
pod repo update
|
||||||
flutter build ios --config-only --release
|
flutter build ios --config-only --release
|
||||||
cd ios
|
cd ios
|
||||||
pod update
|
pod update
|
||||||
|
|||||||
@@ -1,27 +1,39 @@
|
|||||||
name: Eatvio App Android Test
|
name: Eatvio App Android Test
|
||||||
|
|
||||||
on: [ pull_request ]
|
on: [pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.35.1"
|
flutter_version: '3.41.2'
|
||||||
java_version: "21.x"
|
java_version: '21.x'
|
||||||
flutter_channel: "stable"
|
flutter_channel: 'stable'
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: '/opt/hostedtoolcache/'
|
||||||
ANDROID_SDK_ROOT: "/opt/hostedtoolcache/Android/Sdk"
|
ANDROID_SDK_ROOT: '/opt/hostedtoolcache/Android/Sdk'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: [ dev-office ]
|
runs-on: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
|
||||||
|
# Пока что не нужно проксирование
|
||||||
|
# - name: Set proxy server
|
||||||
|
# env:
|
||||||
|
# PROXY_USER: ${{ secrets.PROXY_USER }}
|
||||||
|
# PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
|
||||||
|
# run: |
|
||||||
|
# echo "Setting up proxy..."
|
||||||
|
# cat <<EOF >/etc/apt/apt.conf.d/proxy.conf
|
||||||
|
# Acquire::http::Proxy "socks5h://${PROXY_USER}:${PROXY_PASSWORD}@10.55.5.202:19843/";
|
||||||
|
# EOF
|
||||||
|
|
||||||
- name: Checkout env repository
|
- name: Checkout env repository
|
||||||
uses: actions/checkout@v3
|
uses: https://github.com/actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: mso7nueh/eatvio_app_env
|
repository: eatvio/eatvio_app_env
|
||||||
path: env_repo
|
path: env_repo
|
||||||
token: ${{ vars.MSO7NUEH_TOKEN }}
|
# Здесь может быть токен любого юзера с доступом в gitea
|
||||||
|
token: ${{ vars.READ_USER_GITEA_TOKEN }}
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Copy .env file
|
- name: Copy .env file
|
||||||
@@ -56,22 +68,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache Android SDK
|
- name: Cache Android SDK
|
||||||
id: cache-sdk
|
id: cache-sdk
|
||||||
uses: "https://gitea.com/actions/cache@v4"
|
uses: 'https://github.com/actions/cache@v4'
|
||||||
with:
|
with:
|
||||||
path: ${{ env.my_local_cache_dir }}
|
path: ${{ env.my_local_cache_dir }}
|
||||||
key: ${{ runner.os }}-cache-sdk
|
key: ${{ runner.os }}-cache-sdk
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: https://github.com/actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.java_version }}
|
java-version: ${{ env.java_version }}
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
|
||||||
- name: Install utils for Android SDK
|
- name: Install utils for Android SDK
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y openjdk-11-jdk wget jq clang cmake ninja-build libgtk-3-dev
|
apt-get install -y openjdk-17-jdk wget jq clang cmake ninja-build libgtk-3-dev
|
||||||
|
|
||||||
- name: Install Android SDK
|
- name: Install Android SDK
|
||||||
if: steps.cache-sdk.outputs.cache-hit != 'true'
|
if: steps.cache-sdk.outputs.cache-hit != 'true'
|
||||||
@@ -90,25 +102,31 @@ jobs:
|
|||||||
sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31"
|
sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31"
|
||||||
sdkmanager "build-tools;34.0.0"
|
sdkmanager "build-tools;34.0.0"
|
||||||
sdkmanager "ndk;25.1.8937393"
|
sdkmanager "ndk;25.1.8937393"
|
||||||
sdkmanager --install "cmake;3.22.1"
|
sdkmanager --install "cmake;3.22.1"
|
||||||
|
|
||||||
- name: Set up Android environment variables
|
- name: Set up Android environment variables
|
||||||
id: "android-env-setup"
|
id: 'android-env-setup'
|
||||||
run: |
|
run: |
|
||||||
echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
||||||
echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV
|
echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV
|
||||||
echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
|
||||||
|
|
||||||
- id: flutter-action
|
- name: Set up Flutter
|
||||||
uses: actions/flutter-action@v2
|
run: |
|
||||||
with:
|
FLUTTER_DIR="${{ env.my_local_cache_dir }}flutter"
|
||||||
channel: ${{ env.flutter_channel }}
|
if [ ! -f "$FLUTTER_DIR/bin/flutter" ]; then
|
||||||
flutter-version: ${{ env.flutter_version }}
|
echo "Downloading Flutter ${{ env.flutter_version }}..."
|
||||||
cache: true
|
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/${{ env.flutter_channel }}/linux/flutter_linux_${{ env.flutter_version }}-${{ env.flutter_channel }}.tar.xz"
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
echo "Extracting Flutter..."
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
mkdir -p "${{ env.my_local_cache_dir }}"
|
||||||
pub-cache-key: "flutter-pub:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache of dart pub get dependencies
|
tar xf /tmp/flutter.tar.xz -C "${{ env.my_local_cache_dir }}"
|
||||||
pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
rm /tmp/flutter.tar.xz
|
||||||
|
else
|
||||||
|
echo "Flutter already cached at $FLUTTER_DIR"
|
||||||
|
fi
|
||||||
|
echo "$FLUTTER_DIR/bin" >> $GITHUB_PATH
|
||||||
|
echo "$FLUTTER_DIR/bin/cache/dart-sdk/bin" >> $GITHUB_PATH
|
||||||
|
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
# - name: Print flutter-action outputs
|
# - name: Print flutter-action outputs
|
||||||
# shell: bash
|
# shell: bash
|
||||||
@@ -126,21 +144,85 @@ jobs:
|
|||||||
|
|
||||||
- name: Fix dir permissions
|
- name: Fix dir permissions
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/${{ env.flutter_channel }}-${{ env.flutter_version }}-${{ steps.flutter-action.outputs.ARCHITECTURE }}
|
ARCH=$(uname -m)
|
||||||
|
if [ "$ARCH" = "x86_64" ]; then ARCH="x64"; fi
|
||||||
|
git config --global --add safe.directory "${{ env.my_local_cache_dir }}flutter"
|
||||||
flutter config --android-sdk="$ANDROID_HOME"
|
flutter config --android-sdk="$ANDROID_HOME"
|
||||||
flutter doctor -v
|
flutter doctor -v
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
flutter clean
|
flutter clean
|
||||||
flutter pub get
|
flutter pub cache repair
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
flutter pub get
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Native Analyze
|
- name: Native Analyze
|
||||||
run: flutter analyze
|
run: flutter analyze
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: flutter test
|
run: flutter test --coverage
|
||||||
|
|
||||||
|
- name: Test coverage
|
||||||
|
run: |
|
||||||
|
echo "📊 Analyzing test coverage..."
|
||||||
|
echo ""
|
||||||
|
awk -F: '
|
||||||
|
/^SF:/ {
|
||||||
|
file = $0
|
||||||
|
sub(/^SF:/, "", file)
|
||||||
|
sub(/^[[:space:]]+/, "", file)
|
||||||
|
is_generated = 0
|
||||||
|
if (file ~ /\.(g|freezed|gr|mocks)\.dart$/) {
|
||||||
|
is_generated = 1
|
||||||
|
generated_files++
|
||||||
|
} else if (file ~ /\/(build|\.dart_tool)\//) {
|
||||||
|
is_generated = 1
|
||||||
|
generated_files++
|
||||||
|
} else {
|
||||||
|
source_files++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/^LF:/ {
|
||||||
|
if (!is_generated) {
|
||||||
|
total += $2
|
||||||
|
} else {
|
||||||
|
generated_lines += $2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/^LH:/ {
|
||||||
|
if (!is_generated) {
|
||||||
|
covered += $2
|
||||||
|
} else {
|
||||||
|
generated_covered += $2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print "📁 Files:"
|
||||||
|
printf " Source files: %d\n", source_files
|
||||||
|
printf " Generated files: %d (excluded)\n", generated_files
|
||||||
|
print ""
|
||||||
|
print "📝 Lines:"
|
||||||
|
printf " Total lines: %d\n", total
|
||||||
|
printf " Covered lines: %d\n", covered
|
||||||
|
printf " Uncovered lines: %d\n", total - covered
|
||||||
|
print ""
|
||||||
|
if (total == 0) {
|
||||||
|
print "⚠️ Coverage: 0%"
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
percentage = (covered / total) * 100
|
||||||
|
printf "✅ Coverage: %.2f%%\n", percentage
|
||||||
|
print ""
|
||||||
|
if (percentage < 70) {
|
||||||
|
printf "❌ FAIL: Coverage is below 70%%\n"
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
printf "💚 Excellent: Coverage is above 70%%\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' coverage/lcov.info
|
||||||
|
|
||||||
- name: Build apk
|
- name: Build apk
|
||||||
run: |
|
run: |
|
||||||
@@ -155,10 +237,10 @@ jobs:
|
|||||||
APK_NAME="eatvio_app_${TIMESTAMP}.apk"
|
APK_NAME="eatvio_app_${TIMESTAMP}.apk"
|
||||||
mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME
|
mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME
|
||||||
echo "APK build finished $APK_NAME"
|
echo "APK build finished $APK_NAME"
|
||||||
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK artifact
|
- name: Upload APK artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: https://github.com/actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_NAME }}
|
path: ${{ env.APK_NAME }}
|
||||||
|
|||||||
7
.gitignore
vendored
@@ -19,4 +19,9 @@ ios/XCBuildData/
|
|||||||
env/.env
|
env/.env
|
||||||
.env
|
.env
|
||||||
android/key.properties
|
android/key.properties
|
||||||
app/upload-keystore.jks
|
app/upload-keystore.jks
|
||||||
|
coverage/
|
||||||
|
backend/
|
||||||
|
.fvm/
|
||||||
|
.fvmrc
|
||||||
|
/eatvio
|
||||||
5
Makefile
@@ -7,4 +7,7 @@ run-analyse:
|
|||||||
run-checks: run-analyse run-tests
|
run-checks: run-analyse run-tests
|
||||||
|
|
||||||
build-generated-files:
|
build-generated-files:
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
|
clean-coverage:
|
||||||
|
@./scripts/remove_generated_from_coverage.sh
|
||||||
|
|||||||
55
analyze_coverage.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
|
||||||
|
def analyze_coverage():
|
||||||
|
files = {}
|
||||||
|
current_file = None
|
||||||
|
|
||||||
|
with open('coverage/lcov.info', 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
if line.startswith('SF:'):
|
||||||
|
current_file = line[3:]
|
||||||
|
files[current_file] = {'total': 0, 'covered': 0}
|
||||||
|
i += 1
|
||||||
|
elif line.startswith('LF:') and current_file:
|
||||||
|
files[current_file]['total'] = int(line[3:])
|
||||||
|
i += 1
|
||||||
|
elif line.startswith('LH:') and current_file:
|
||||||
|
files[current_file]['covered'] = int(line[3:])
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Filter out generated files
|
||||||
|
filtered = []
|
||||||
|
for f, d in files.items():
|
||||||
|
if not f.endswith('.g.dart') and not 'generated' in f.lower() and not f.endswith('l10n.dart'):
|
||||||
|
filtered.append((f, d))
|
||||||
|
|
||||||
|
# Find files with less than 50% coverage
|
||||||
|
low_coverage = []
|
||||||
|
for f, d in filtered:
|
||||||
|
if d['total'] > 0:
|
||||||
|
pct = d['covered']/d['total']*100
|
||||||
|
if pct < 50:
|
||||||
|
low_coverage.append((f, d))
|
||||||
|
|
||||||
|
# Sort by coverage percentage (lowest first)
|
||||||
|
sorted_files = sorted(
|
||||||
|
low_coverage,
|
||||||
|
key=lambda x: (x[1]['covered']/x[1]['total']*100 if x[1]['total'] > 0 else 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
print('Files with less than 50% coverage:')
|
||||||
|
print('=' * 80)
|
||||||
|
for f, d in sorted_files[:50]:
|
||||||
|
pct = d['covered']/d['total']*100
|
||||||
|
print(f'{pct:6.1f}% ({d["covered"]:4d}/{d["total"]:4d}) - {f}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
analyze_coverage()
|
||||||
|
|
||||||
52
analyze_coverage_by_uncovered.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
|
||||||
|
def analyze_coverage_by_uncovered():
|
||||||
|
files = {}
|
||||||
|
current_file = None
|
||||||
|
|
||||||
|
with open('coverage/lcov.info', 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
if line.startswith('SF:'):
|
||||||
|
current_file = line[3:]
|
||||||
|
files[current_file] = {'total': 0, 'covered': 0}
|
||||||
|
i += 1
|
||||||
|
elif line.startswith('LF:') and current_file:
|
||||||
|
files[current_file]['total'] = int(line[3:])
|
||||||
|
i += 1
|
||||||
|
elif line.startswith('LH:') and current_file:
|
||||||
|
files[current_file]['covered'] = int(line[3:])
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Filter out generated files and UI screens/widgets (hard to test)
|
||||||
|
filtered = []
|
||||||
|
exclude_patterns = ['.g.dart', 'generated', 'l10n.dart', '/pages/', '/widgets/', '_screen.dart', '_widget.dart']
|
||||||
|
|
||||||
|
for f, d in files.items():
|
||||||
|
if d['total'] > 0:
|
||||||
|
# Skip UI files
|
||||||
|
should_exclude = any(pattern in f for pattern in exclude_patterns)
|
||||||
|
if not should_exclude:
|
||||||
|
uncovered = d['total'] - d['covered']
|
||||||
|
pct = d['covered']/d['total']*100
|
||||||
|
filtered.append((f, d, uncovered, pct))
|
||||||
|
|
||||||
|
# Sort by uncovered lines (most uncovered first)
|
||||||
|
sorted_files = sorted(filtered, key=lambda x: x[2], reverse=True)
|
||||||
|
|
||||||
|
print('Files sorted by uncovered lines (excluding UI screens/widgets):')
|
||||||
|
print('=' * 100)
|
||||||
|
print(f'{"Uncovered":>10} {"Covered":>8} {"Total":>8} {"%":>7} {"File":<60}')
|
||||||
|
print('=' * 100)
|
||||||
|
for f, d, uncovered, pct in sorted_files[:30]:
|
||||||
|
print(f'{uncovered:>10} {d["covered"]:>8} {d["total"]:>8} {pct:>6.1f}% {f}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
analyze_coverage_by_uncovered()
|
||||||
|
|
||||||
52
analyze_output.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
Files with less than 50% coverage:
|
||||||
|
================================================================================
|
||||||
|
0.0% ( 0/ 37) - lib/core/firebase_api.dart
|
||||||
|
0.0% ( 0/ 1) - lib/features/auth/models/oauth_verification_confirm_request.dart
|
||||||
|
0.0% ( 0/ 1) - lib/features/auth/models/oauth_verification_confirm_response.dart
|
||||||
|
0.0% ( 0/ 1) - lib/features/auth/models/oauth_verification_request_response.dart
|
||||||
|
0.0% ( 0/ 342) - lib/features/dev_tools/test_cases.dart
|
||||||
|
0.9% ( 2/ 220) - lib/features/dev_tools/presentation/dev_tools_screen.dart
|
||||||
|
2.4% ( 1/ 42) - lib/features/auth/presentation/pages/scan_qr_code_screen.dart
|
||||||
|
2.9% ( 1/ 34) - lib/features/diary/presentation/pages/barcode_scanner_screen.dart
|
||||||
|
3.6% ( 1/ 28) - lib/features/personal_diet_suggestion/presentation/personal_diet_suggestion_screen.dart
|
||||||
|
3.8% ( 1/ 26) - lib/features/update_screen/presentation/update_screen.dart
|
||||||
|
4.4% ( 4/ 90) - lib/core/widgets/custom_popup.dart
|
||||||
|
5.9% ( 1/ 17) - lib/features/recipe/presentation/pages/my_recipes_screen.dart
|
||||||
|
6.2% ( 2/ 32) - lib/features/consultations/presentation/pages/consultations_screen.dart
|
||||||
|
7.1% ( 1/ 14) - lib/features/menu/presentation/pages/blocked_users_screen.dart
|
||||||
|
7.7% ( 1/ 13) - lib/features/registration_v2/presentation/pages/registration_v2_page.dart
|
||||||
|
8.3% ( 18/ 217) - lib/features/blog_post/presentation/pages/blog_post_screen.dart
|
||||||
|
11.1% ( 1/ 9) - lib/features/registration_v2/presentation/widgets/step_progress.dart
|
||||||
|
11.8% ( 14/ 119) - lib/features/auth/sdk/social_auth_facade.dart
|
||||||
|
13.0% ( 3/ 23) - lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart
|
||||||
|
14.5% ( 11/ 76) - lib/core/app_router.dart
|
||||||
|
19.3% ( 11/ 57) - lib/features/create_recipe/presentation/widgets/image_widget.dart
|
||||||
|
19.6% ( 10/ 51) - lib/features/create_recipe/entities/recipe_create_entity.dart
|
||||||
|
19.7% ( 12/ 61) - lib/features/create_recipe/presentation/widgets/step_image_widget.dart
|
||||||
|
20.0% ( 6/ 30) - lib/core/image_compressing/image_compress_utility.dart
|
||||||
|
20.0% ( 57/ 285) - lib/features/complaining/presentation/complaining_bottom_sheet.dart
|
||||||
|
21.9% ( 7/ 32) - lib/core/routing/content_routes.dart
|
||||||
|
25.6% ( 46/ 180) - lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart
|
||||||
|
26.8% ( 11/ 41) - lib/features/create_recipe/reporitories/recipe_repository.dart
|
||||||
|
27.4% ( 96/ 351) - lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart
|
||||||
|
28.0% ( 28/ 100) - lib/core/utils/image_constant.dart
|
||||||
|
28.4% ( 23/ 81) - lib/core/websockets/ws_manager.dart
|
||||||
|
29.0% ( 157/ 541) - lib/features/profile/presentation/pages/profile_screen.dart
|
||||||
|
29.3% ( 137/ 468) - lib/features/notifications/models/notification_model.dart
|
||||||
|
33.1% ( 47/ 142) - lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart
|
||||||
|
33.3% ( 9/ 27) - lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart
|
||||||
|
35.2% ( 58/ 165) - lib/features/registration/presentation/pages/registration_screen.dart
|
||||||
|
35.6% ( 36/ 101) - lib/features/chat/presentation/pages/empty_chat_screen.dart
|
||||||
|
36.0% ( 9/ 25) - lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart
|
||||||
|
36.7% ( 11/ 30) - lib/core/routing/main_navigation_routes.dart
|
||||||
|
37.5% ( 3/ 8) - lib/core/permission_handler/permission_handler.dart
|
||||||
|
38.6% ( 22/ 57) - lib/features/registration/presentation/pages/date_of_birth_screen.dart
|
||||||
|
38.9% ( 35/ 90) - lib/core/routing/other_routes.dart
|
||||||
|
39.0% ( 30/ 77) - lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart
|
||||||
|
39.3% ( 119/ 303) - lib/features/settings/presentation/pages/settings_screen.dart
|
||||||
|
39.3% ( 22/ 56) - lib/features/consultant/become_consultant/presentation/pages/photo_or_scans_screen.dart
|
||||||
|
40.0% ( 4/ 10) - lib/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart
|
||||||
|
40.0% ( 4/ 10) - lib/features/registration/services/registration_analytics_service.dart
|
||||||
|
41.5% ( 95/ 229) - lib/features/create_diet/presentation/cubit/create_diet_cubit.dart
|
||||||
|
41.9% ( 31/ 74) - lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart
|
||||||
|
42.7% ( 67/ 157) - lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart
|
||||||
@@ -1 +1,94 @@
|
|||||||
{"agcgw":{"url":"connect-drru.hispace.dbankcloud.ru","backurl":"connect-drru.hispace.dbankcloud.cn","websocketurl":"connect-ws-drru.hispace.dbankcloud.ru","websocketbackurl":"connect-ws-drru.hispace.dbankcloud.cn"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"20007000036693451","product_id":"461323198429446894","client_id":"1595824543740483264","client_secret":"2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE","project_id":"461323198429446894","app_id":"113238569","api_key":"DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==","package_name":"com.eatvioapp.app"},"oauth_client":{"client_id":"113238569","client_type":1},"app_info":{"app_id":"113238569","package_name":"com.eatvioapp.app"},"service":{"analytics":{"collector_url":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drru.cloud.huawei.ru"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"RU","configuration_version":"3.0","appInfos":[{"package_name":"com.eatvioapp.app","client":{"app_id":"113238569"},"app_info":{"package_name":"com.eatvioapp.app","app_id":"113238569"},"oauth_client":{"client_type":1,"client_id":"113238569"}}]}
|
{
|
||||||
|
"agcgw" : {
|
||||||
|
"url" : "connect-drru.hispace.dbankcloud.ru",
|
||||||
|
"backurl" : "connect-drru.hispace.dbankcloud.cn",
|
||||||
|
"websocketurl" : "connect-ws-drru.hispace.dbankcloud.ru",
|
||||||
|
"websocketbackurl" : "connect-ws-drru.hispace.dbankcloud.cn"
|
||||||
|
},
|
||||||
|
"agcgw_all" : {
|
||||||
|
"SG" : "connect-dra.dbankcloud.cn",
|
||||||
|
"SG_back" : "connect-dra.hispace.hicloud.com",
|
||||||
|
"CN" : "connect-drcn.dbankcloud.cn",
|
||||||
|
"CN_back" : "connect-drcn.hispace.hicloud.com",
|
||||||
|
"RU" : "connect-drru.hispace.dbankcloud.ru",
|
||||||
|
"RU_back" : "connect-drru.hispace.dbankcloud.cn",
|
||||||
|
"DE" : "connect-dre.dbankcloud.cn",
|
||||||
|
"DE_back" : "connect-dre.hispace.hicloud.com"
|
||||||
|
},
|
||||||
|
"websocketgw_all" : {
|
||||||
|
"SG" : "connect-ws-dra.hispace.dbankcloud.cn",
|
||||||
|
"SG_back" : "connect-ws-dra.hispace.dbankcloud.com",
|
||||||
|
"CN" : "connect-ws-drcn.hispace.dbankcloud.cn",
|
||||||
|
"CN_back" : "connect-ws-drcn.hispace.dbankcloud.com",
|
||||||
|
"RU" : "connect-ws-drru.hispace.dbankcloud.ru",
|
||||||
|
"RU_back" : "connect-ws-drru.hispace.dbankcloud.cn",
|
||||||
|
"DE" : "connect-ws-dre.hispace.dbankcloud.cn",
|
||||||
|
"DE_back" : "connect-ws-dre.hispace.dbankcloud.com"
|
||||||
|
},
|
||||||
|
"client" : {
|
||||||
|
"cp_id" : "20007000036693451",
|
||||||
|
"product_id" : "461323198429446894",
|
||||||
|
"client_id" : "1595824543740483264",
|
||||||
|
"client_secret" : "2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE",
|
||||||
|
"project_id" : "461323198429446894",
|
||||||
|
"app_id" : "113238569",
|
||||||
|
"api_key" : "DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==",
|
||||||
|
"package_name" : "com.eatvioapp.app"
|
||||||
|
},
|
||||||
|
"oauth_client" : {
|
||||||
|
"client_id" : "113238569",
|
||||||
|
"client_type" : 1
|
||||||
|
},
|
||||||
|
"app_info" : {
|
||||||
|
"app_id" : "113238569",
|
||||||
|
"package_name" : "com.eatvioapp.app"
|
||||||
|
},
|
||||||
|
"service" : {
|
||||||
|
"analytics" : {
|
||||||
|
"collector_url" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
|
||||||
|
"collector_url_cn" : "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
|
||||||
|
"collector_url_de" : "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
|
||||||
|
"collector_url_ru" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
|
||||||
|
"collector_url_sg" : "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
|
||||||
|
"resource_id" : "p1",
|
||||||
|
"channel_id" : ""
|
||||||
|
},
|
||||||
|
"ml" : {
|
||||||
|
"mlservice_url" : "ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"
|
||||||
|
},
|
||||||
|
"cloudstorage" : {
|
||||||
|
"storage_url" : "https://agc-storage-drcn.platform.dbankcloud.cn",
|
||||||
|
"storage_url_ru" : "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
|
"storage_url_sg" : "https://ops-dra.agcstorage.link",
|
||||||
|
"storage_url_de" : "https://ops-dre.agcstorage.link",
|
||||||
|
"storage_url_cn" : "https://agc-storage-drcn.platform.dbankcloud.cn",
|
||||||
|
"storage_url_ru_back" : "https://agc-storage-drru.cloud.huawei.ru",
|
||||||
|
"storage_url_sg_back" : "https://agc-storage-dra.cloud.huawei.asia",
|
||||||
|
"storage_url_de_back" : "https://agc-storage-dre.cloud.huawei.eu",
|
||||||
|
"storage_url_cn_back" : "https://agc-storage-drcn.cloud.huawei.com.cn"
|
||||||
|
},
|
||||||
|
"search" : {
|
||||||
|
"url" : "https://search-drru.cloud.huawei.ru"
|
||||||
|
},
|
||||||
|
"edukit" : {
|
||||||
|
"edu_url" : "edukit.cloud.huawei.com.cn",
|
||||||
|
"dh_url" : "edukit.cloud.huawei.com.cn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"region" : "RU",
|
||||||
|
"configuration_version" : "3.0",
|
||||||
|
"appInfos" : [ {
|
||||||
|
"package_name" : "com.eatvioapp.app",
|
||||||
|
"client" : {
|
||||||
|
"app_id" : "113238569"
|
||||||
|
},
|
||||||
|
"app_info" : {
|
||||||
|
"package_name" : "com.eatvioapp.app",
|
||||||
|
"app_id" : "113238569"
|
||||||
|
},
|
||||||
|
"oauth_client" : {
|
||||||
|
"client_type" : 1,
|
||||||
|
"client_id" : "113238569"
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ plugins {
|
|||||||
id 'com.google.gms.google-services'
|
id 'com.google.gms.google-services'
|
||||||
// END: FlutterFire Configuration
|
// END: FlutterFire Configuration
|
||||||
id "kotlin-android"
|
id "kotlin-android"
|
||||||
|
id "kotlin-parcelize"
|
||||||
id "dev.flutter.flutter-gradle-plugin"
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
id 'com.huawei.agconnect'
|
id 'com.huawei.agconnect'
|
||||||
}
|
}
|
||||||
@@ -19,15 +20,15 @@ def yandexClientId = project.hasProperty('YANDEX_CLIENT_ID') ? project.property(
|
|||||||
android {
|
android {
|
||||||
namespace = "com.eatvioapp.app"
|
namespace = "com.eatvioapp.app"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@@ -68,10 +69,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.huawei.agconnect:agconnect-core:1.9.1.301'
|
implementation 'com.huawei.agconnect:agconnect-core:1.9.5.300'
|
||||||
implementation 'com.huawei.hms:push:6.12.0.300'
|
implementation 'com.huawei.hms:push:6.13.0.301'
|
||||||
// Huawei Analytics dependency to satisfy com.huawei.hianalytics.* references during R8
|
// Huawei Analytics dependency to satisfy com.huawei.hianalytics.* references during R8
|
||||||
implementation 'com.huawei.hms:hianalytics:6.12.0.300'
|
implementation 'com.huawei.hms:hianalytics:6.12.0.301'
|
||||||
// BouncyCastle provider required by Huawei secure utils used transitively by HMS
|
// BouncyCastle provider required by Huawei secure utils used transitively by HMS
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1'
|
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
@@ -21,6 +23,12 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="push_kit_auto_init_enabled"
|
android:name="push_kit_auto_init_enabled"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.huawei.hms.client.appid"
|
||||||
|
android:value="appid=113238569" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.huawei.hms.client.cpid"
|
||||||
|
android:value="cpid=20007000036693451" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -49,32 +57,19 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
|
|
||||||
|
<!-- Main domains -->
|
||||||
<data android:host="eatvio.ru" />
|
<data android:host="eatvio.ru" />
|
||||||
<data android:host="www.eatvio.ru" />
|
<data android:host="www.eatvio.ru" />
|
||||||
|
<data android:host="eatvio.com" />
|
||||||
|
<data android:host="www.eatvio.com" />
|
||||||
|
|
||||||
|
<!-- Development/Subdomains -->
|
||||||
|
<data android:host="*.eatvio.ru" />
|
||||||
|
<data android:host="*.eatvio.com" />
|
||||||
|
|
||||||
<data android:pathPattern="/ru/recipe/.*" />
|
<!-- Simplify patterns to allow navigation within the app for all matching hosts -->
|
||||||
<data android:pathPattern="/en/recipe/.*" />
|
<data android:pathPattern="/.*" />
|
||||||
|
|
||||||
<data android:pathPattern="/ru/blog_post/.*" />
|
|
||||||
<data android:pathPattern="/en/blog_post/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/diet/.*" />
|
|
||||||
<data android:pathPattern="/en/diet/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/plan/.*" />
|
|
||||||
<data android:pathPattern="/en/plan/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/chat/.*" />
|
|
||||||
<data android:pathPattern="/en/chat/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/chat" />
|
|
||||||
<data android:pathPattern="/en/chat" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/profile/.*" />
|
|
||||||
<data android:pathPattern="/en/profile/.*" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/ru/diary/.*" />
|
|
||||||
<data android:pathPattern="/en/diary/.*" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
@@ -93,5 +88,168 @@
|
|||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
|
<!-- SBP bank packages for СБП payment detection -->
|
||||||
|
<package android:name="ru.sberbankmobile" />
|
||||||
|
<package android:name="com.idamob.tinkoff.android" />
|
||||||
|
<package android:name="ru.vtb24.mobilebanking.android" />
|
||||||
|
<package android:name="ru.alfabank.mobile.android" />
|
||||||
|
<package android:name="ru.raiffeisennews" />
|
||||||
|
<package android:name="com.openbank" />
|
||||||
|
<package android:name="ru.gazprombank.android.mobilebank.app" />
|
||||||
|
<package android:name="com.logo.mbanking" />
|
||||||
|
<package android:name="ru.sovcomcard.halva.v1" />
|
||||||
|
<package android:name="ru.rosbank.android" />
|
||||||
|
<package android:name="ru.rshb.dbo" />
|
||||||
|
<package android:name="ru.mkb.mobile" />
|
||||||
|
<package android:name="ru.ucb.android" />
|
||||||
|
<package android:name="ru.unicredit.android" />
|
||||||
|
<package android:name="ru.skbbank.ib" />
|
||||||
|
<package android:name="ru.gebank.ib" />
|
||||||
|
<package android:name="ru.avangard.sbpent" />
|
||||||
|
<package android:name="ru.elplat.elplat2" />
|
||||||
|
<package android:name="com.bifit.rncbbeta" />
|
||||||
|
<package android:name="ru.ftc.faktura.expobank" />
|
||||||
|
<package android:name="com.bssys.vbrrretail" />
|
||||||
|
<package android:name="ru.wildberries.razz" />
|
||||||
|
<package android:name="ru.artsofte.russiafl" />
|
||||||
|
<package android:name="ru.ftc.faktura.multibank" />
|
||||||
|
<package android:name="ru.bank_hlynov.xbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.vesta" />
|
||||||
|
<package android:name="com.isimplelab.ibank.solidarnost" />
|
||||||
|
<package android:name="com.bssys.roscapretail" />
|
||||||
|
<package android:name="ru.ftc.faktura.kbhmb" />
|
||||||
|
<package android:name="ru.mts.money" />
|
||||||
|
<package android:name="ru.ftc.faktura.pskb" />
|
||||||
|
<package android:name="ru.ftc.faktura.nskbl" />
|
||||||
|
<package android:name="ru.akbars.mobile" />
|
||||||
|
<package android:name="ru.ftc.faktura.rostfinance" />
|
||||||
|
<package android:name="ru.ilb.bbmobile" />
|
||||||
|
<package android:name="ru.mcbankrus.MCBankRus" />
|
||||||
|
<package android:name="ru.albank.online.aebit" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.icbru" />
|
||||||
|
<package android:name="ru.ftc.faktura.orbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.agropromkredit" />
|
||||||
|
<package android:name="com.energobank.digital" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.kbnk" />
|
||||||
|
<package android:name="ru.ftc.faktura.sdm" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.mbb" />
|
||||||
|
<package android:name="ru.ftc.faktura.absolutbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.akcept" />
|
||||||
|
<package android:name="ru.faktura.etbank.person" />
|
||||||
|
<package android:name="ru.ftc.faktura.razvitiestolica" />
|
||||||
|
<package android:name="ru.ftc.faktura.resokreditbank" />
|
||||||
|
<package android:name="ru.yoo.money" />
|
||||||
|
<package android:name="ru.bspb" />
|
||||||
|
<package android:name="com.isimplelab.ibank.kubankredit" />
|
||||||
|
<package android:name="com.tochka.bank" />
|
||||||
|
<package android:name="com.bssys.novikomretail" />
|
||||||
|
<package android:name="ru.rosdorbank.physical" />
|
||||||
|
<package android:name="ru.ftc.faktura.primsoc" />
|
||||||
|
<package android:name="ru.ftc.faktura.banksaratov" />
|
||||||
|
<package android:name="ru.ftc.faktura.tavrich" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.thbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.kremlevskiy" />
|
||||||
|
<package android:name="ru.ftc.faktura.tkbbank" />
|
||||||
|
<package android:name="ru.chelyabinvestbank.investpay" />
|
||||||
|
<package android:name="ru.ftc.faktura.derzhava" />
|
||||||
|
<package android:name="ru.nbd.android" />
|
||||||
|
<package android:name="ru.sngb.dbo.client.android" />
|
||||||
|
<package android:name="ru.ftc.faktura.united" />
|
||||||
|
<package android:name="ru.ftc.faktura.venetsbank" />
|
||||||
|
<package android:name="ru.letobank.Prometheus" />
|
||||||
|
<package android:name="ru.simpls.brs2.mobbank" />
|
||||||
|
<package android:name="com.bifit.dvbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.intesabank" />
|
||||||
|
<package android:name="com.mmonline.mobile" />
|
||||||
|
<package android:name="cb.ibank.vuz" />
|
||||||
|
<package android:name="ru.uralprombank.mobilebanknew.googleplay" />
|
||||||
|
<package android:name="ru.ftc.faktura.nsbank" />
|
||||||
|
<package android:name="ru.emb.android" />
|
||||||
|
<package android:name="ru.ftc.faktura.maritimebank" />
|
||||||
|
<package android:name="com.credituralbank.CUBmobilenew" />
|
||||||
|
<package android:name="com.isimplelab.isimpleceo.uglemet" />
|
||||||
|
<package android:name="com.bssys.stroylesretail" />
|
||||||
|
<package android:name="ru.ftc.faktura.iturup" />
|
||||||
|
<package android:name="ru.ftc.faktura.finbank" />
|
||||||
|
<package android:name="ru.faktura.gaztransbank.person" />
|
||||||
|
<package android:name="com.isimplelab.ibank.chelind" />
|
||||||
|
<package android:name="ru.ftc.faktura.nokss" />
|
||||||
|
<package android:name="ru.ftc.faktura.vlbb" />
|
||||||
|
<package android:name="ru.ftc.faktura.kbb" />
|
||||||
|
<package android:name="ru.faktura.avtograd.person" />
|
||||||
|
<package android:name="ru.ftc.faktura.forshtadt" />
|
||||||
|
<package android:name="ru.ftc.faktura.solidbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.alexbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.akibank" />
|
||||||
|
<package android:name="ru.ftc.faktura.nipbank" />
|
||||||
|
<package android:name="ru.ftc.faktura.alefbank" />
|
||||||
|
<package android:name="com.bifit.vfbank" />
|
||||||
|
<package android:name="com.bssys.uralfdretail" />
|
||||||
|
<package android:name="com.snb.online" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.okbank" />
|
||||||
|
<package android:name="com.tatsotsbank.dbomobile" />
|
||||||
|
<package android:name="ru.vtkbank.android" />
|
||||||
|
<package android:name="ru.faktura.jtbank.person" />
|
||||||
|
<package android:name="ru.ftc.faktura.ns" />
|
||||||
|
<package android:name="ru.ftc.faktura.zemskybank" />
|
||||||
|
<package android:name="com.bssys.aversretail" />
|
||||||
|
<package android:name="ru.rusnarbank.correqts.retail" />
|
||||||
|
<package android:name="ru.ftc.faktura.baikalinvestbank" />
|
||||||
|
<package android:name="ru.sbi.android" />
|
||||||
|
<package android:name="com.metkombank.mobile" />
|
||||||
|
<package android:name="com.isimplelab.ionic.kazan.fl" />
|
||||||
|
<package android:name="com.intervale.sbpas" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.zarech" />
|
||||||
|
<package android:name="ru.centrinvest.mobilebanking2018" />
|
||||||
|
<package android:name="com.mifors.izhcombank" />
|
||||||
|
<package android:name="com.bifit.pmobile.isbank" />
|
||||||
|
<package android:name="ru.zenit.android" />
|
||||||
|
<package android:name="ru.briginvest.sense" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.cfb" />
|
||||||
|
<package android:name="com.isimplelab.ionic.gorbank.prod" />
|
||||||
|
<package android:name="ru.ftc.faktura.moscombank" />
|
||||||
|
<package android:name="com.timerbank.retail" />
|
||||||
|
<package android:name="com.mob.android" />
|
||||||
|
<package android:name="com.citibank.mobile.ru" />
|
||||||
|
<package android:name="com.bifit.atbbank" />
|
||||||
|
<package android:name="ru.mw" />
|
||||||
|
<package android:name="ru.bankuralsib.mb.android" />
|
||||||
|
<package android:name="com.banksoyuz.artsofte" />
|
||||||
|
<package android:name="ru.rubank.ubsmobile" />
|
||||||
|
<package android:name="cb.ibank" />
|
||||||
|
<package android:name="ru.ipb.ubsmobile" />
|
||||||
|
<package android:name="com.openwaygroup.ic.panda.poidem" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.tenderbank" />
|
||||||
|
<package android:name="ru.otpbank.mobile" />
|
||||||
|
<package android:name="ru.krk.ubsmobile" />
|
||||||
|
<package android:name="ru.homecredit.mycredit" />
|
||||||
|
<package android:name="cz.bsc.rc" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.belsocbank" />
|
||||||
|
<package android:name="com.hicebank.android" />
|
||||||
|
<package android:name="com.bpc.crossplatform_trading.bpc_trading" />
|
||||||
|
<package android:name="com.bifit.nrb" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.moskb" />
|
||||||
|
<package android:name="ru.isfront.android.kt" />
|
||||||
|
<package android:name="ru.ftc.faktura.novobank" />
|
||||||
|
<package android:name="com.smp.android" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.nkbank" />
|
||||||
|
<package android:name="com.isimplelab.ibank.toyota" />
|
||||||
|
<package android:name="com.bifit.mobile.private.bbr" />
|
||||||
|
<package android:name="ru.ftc.faktura.lanta" />
|
||||||
|
<package android:name="com.yandex.bank" />
|
||||||
|
<package android:name="ru.ftc.faktura.bgfbank" />
|
||||||
|
<package android:name="com.idamobile.android.LockoBank" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.MCBank" />
|
||||||
|
<package android:name="com.cetelem.cetelem_android" />
|
||||||
|
<package android:name="com.bssys.gutaretail" />
|
||||||
|
<package android:name="ru.ftc.faktura.siab" />
|
||||||
|
<package android:name="com.isimplelab.ionic.round.prod" />
|
||||||
|
<package android:name="com.priobank.prio" />
|
||||||
|
<package android:name="com.inbank.mobilebank" />
|
||||||
|
<package android:name="com.isimplelab.isimplemobile.payjet" />
|
||||||
|
<package android:name="ru.ftc.faktura.agroros" />
|
||||||
|
<package android:name="com.bifit.mobile.citizen.invb" />
|
||||||
|
<package android:name="com.compassplus.mobicash.customer" />
|
||||||
|
<package android:name="ru.finambank.app" />
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.eatvioapp.app
|
package com.eatvioapp.app
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.huawei.hms.push.HmsMessageService
|
import com.huawei.hms.push.HmsMessageService
|
||||||
import com.huawei.hms.push.RemoteMessage
|
import com.huawei.hms.push.RemoteMessage
|
||||||
|
|
||||||
@@ -13,6 +17,45 @@ class HmsPushService : HmsMessageService() {
|
|||||||
override fun onMessageReceived(message: RemoteMessage?) {
|
override fun onMessageReceived(message: RemoteMessage?) {
|
||||||
super.onMessageReceived(message)
|
super.onMessageReceived(message)
|
||||||
Log.i("HMS", "onMessageReceived: ${message?.data}")
|
Log.i("HMS", "onMessageReceived: ${message?.data}")
|
||||||
|
|
||||||
|
if (message == null) return
|
||||||
|
|
||||||
|
// Берём action_url из data-поля, если оно есть.
|
||||||
|
val actionUrl = message.dataOfMap["action_url"]
|
||||||
|
|
||||||
|
// Строим системное уведомление, по тапу по которому откроется MainActivity с action_url.
|
||||||
|
showNotification(message, actionUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotification(message: RemoteMessage, actionUrl: String?) {
|
||||||
|
val context = applicationContext
|
||||||
|
|
||||||
|
// Интент для запуска Flutter-активити.
|
||||||
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
if (!actionUrl.isNullOrBlank()) {
|
||||||
|
putExtra("action_url", actionUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
val channelId = "hms_push_channel"
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(context, channelId)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher) // используем иконку приложения
|
||||||
|
.setContentTitle(message.notification?.title ?: context.getString(R.string.app_name))
|
||||||
|
.setContentText(message.notification?.body ?: "")
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
NotificationManagerCompat.from(context).notify(message.messageId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.eatvioapp.app
|
package com.eatvioapp.app
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
@@ -14,12 +14,13 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
private val channelName = "com.eatvioapp/hms_push"
|
private val channelName = "com.eatvioapp/hms_push"
|
||||||
|
private var methodChannel: MethodChannel? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
||||||
.setMethodCallHandler { call, result ->
|
methodChannel?.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getHuaweiToken" -> {
|
"getHuaweiToken" -> {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -47,17 +48,21 @@ class MainActivity: FlutterActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appId.isNullOrBlank()) {
|
if (appId.isNullOrBlank()) {
|
||||||
|
android.util.Log.e("HMS", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.")
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
result.error("HMS_TOKEN_ERROR", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.", null)
|
result.error("HMS_TOKEN_ERROR", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.", null)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
android.util.Log.i("HMS", "Getting token for appId: $appId")
|
||||||
val token = HmsInstanceId.getInstance(applicationContext)
|
val token = HmsInstanceId.getInstance(applicationContext)
|
||||||
.getToken(appId, "HCM")
|
.getToken(appId, "HCM")
|
||||||
|
android.util.Log.i("HMS", "Token retrieved successfully")
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
result.success(token)
|
result.success(token)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("HMS", "Error getting HMS token: ${e.message}", e)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
result.error("HMS_TOKEN_ERROR", e.message, null)
|
result.error("HMS_TOKEN_ERROR", e.message, null)
|
||||||
}
|
}
|
||||||
@@ -67,5 +72,33 @@ class MainActivity: FlutterActivity() {
|
|||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем возможный action_url, с которым была открыта Activity (например, из HMS уведомления).
|
||||||
|
handleIntentForActionUrl(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
// Обработка нового интента, когда Activity уже запущена (тап по уведомлению).
|
||||||
|
handleIntentForActionUrl(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleIntentForActionUrl(intent: Intent?) {
|
||||||
|
if (intent == null) return
|
||||||
|
|
||||||
|
// Вариант 1: action_url лежит в extras.
|
||||||
|
val fromExtras = intent.extras?.getString("action_url")
|
||||||
|
|
||||||
|
// Вариант 2: action_url передан как data Uri.
|
||||||
|
val dataUri = intent.data
|
||||||
|
val fromData = dataUri?.toString()
|
||||||
|
|
||||||
|
val actionUrl = fromExtras ?: fromData
|
||||||
|
if (actionUrl.isNullOrBlank()) return
|
||||||
|
|
||||||
|
android.util.Log.i("HMS", "MainActivity.handleIntentForActionUrl: $actionUrl")
|
||||||
|
|
||||||
|
// Проксируем URL во Flutter, где уже есть общая логика навигации по диплинкам.
|
||||||
|
methodChannel?.invokeMethod("openActionUrl", actionUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/fonts/Mulish-Black.ttf
Normal file
BIN
assets/fonts/Mulish-BlackItalic.ttf
Normal file
BIN
assets/fonts/Mulish-Bold.ttf
Normal file
BIN
assets/fonts/Mulish-BoldItalic.ttf
Normal file
BIN
assets/fonts/Mulish-ExtraBold.ttf
Normal file
BIN
assets/fonts/Mulish-ExtraBoldItalic.ttf
Normal file
BIN
assets/fonts/Mulish-ExtraLight.ttf
Normal file
BIN
assets/fonts/Mulish-ExtraLightItalic.ttf
Normal file
BIN
assets/fonts/Mulish-Italic.ttf
Normal file
BIN
assets/fonts/Mulish-Light.ttf
Normal file
BIN
assets/fonts/Mulish-LightItalic.ttf
Normal file
BIN
assets/fonts/Mulish-Medium.ttf
Normal file
BIN
assets/fonts/Mulish-MediumItalic.ttf
Normal file
BIN
assets/fonts/Mulish-Regular.ttf
Normal file
BIN
assets/fonts/Mulish-SemiBold.ttf
Normal file
BIN
assets/fonts/Mulish-SemiBoldItalic.ttf
Normal file
3
assets/icons/check.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="12" height="9" viewBox="0 0 12 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.16229 6.8625L10.5185 0.50625C10.6685 0.35625 10.8435 0.28125 11.0435 0.28125C11.2435 0.28125 11.4185 0.35625 11.5685 0.50625C11.7185 0.65625 11.7935 0.834375 11.7935 1.04063C11.7935 1.24688 11.7185 1.425 11.5685 1.575L4.68729 8.475C4.53729 8.625 4.36229 8.7 4.16229 8.7C3.96229 8.7 3.78729 8.625 3.63729 8.475L0.412289 5.25C0.262289 5.1 0.190414 4.92188 0.196664 4.71563C0.202914 4.50938 0.281039 4.33125 0.431039 4.18125C0.581039 4.03125 0.759164 3.95625 0.965414 3.95625C1.17166 3.95625 1.34979 4.03125 1.49979 4.18125L4.16229 6.8625Z" fill="#16A249"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 668 B |
11
assets/icons/check_filled.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect opacity="0.2" width="20" height="20" rx="6" fill="#16A249"/>
|
||||||
|
<g clip-path="url(#clip0_1823_10218)">
|
||||||
|
<path d="M8.16229 12.8625L14.5185 6.50625C14.6685 6.35625 14.8435 6.28125 15.0435 6.28125C15.2435 6.28125 15.4185 6.35625 15.5685 6.50625C15.7185 6.65625 15.7935 6.83438 15.7935 7.04063C15.7935 7.24688 15.7185 7.425 15.5685 7.575L8.68729 14.475C8.53729 14.625 8.36229 14.7 8.16229 14.7C7.96229 14.7 7.78729 14.625 7.63729 14.475L4.41229 11.25C4.26229 11.1 4.19041 10.9219 4.19666 10.7156C4.20291 10.5094 4.28104 10.3313 4.43104 10.1813C4.58104 10.0313 4.75916 9.95625 4.96541 9.95625C5.17166 9.95625 5.34979 10.0313 5.49979 10.1813L8.16229 12.8625Z" fill="#16A249"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1823_10218">
|
||||||
|
<rect width="12" height="9" fill="white" transform="translate(4 6)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 911 B |
12
assets/icons/close_mark.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1846_342)">
|
||||||
|
<path d="M5.33333 16C3.85185 16 2.59259 15.4815 1.55556 14.4444C0.518518 13.4074 0 12.1481 0 10.6667V5.33333C0 3.85185 0.518518 2.59259 1.55556 1.55556C2.59259 0.518518 3.85185 0 5.33333 0H10.6667C12.1481 0 13.4074 0.518518 14.4444 1.55556C15.4815 2.59259 16 3.85185 16 5.33333V10.6667C16 12.1481 15.4815 13.4074 14.4444 14.4444C13.4074 15.4815 12.1481 16 10.6667 16H5.33333ZM5.33333 14.2222H10.6667C11.6444 14.2222 12.4815 13.8741 13.1778 13.1778C13.8741 12.4815 14.2222 11.6444 14.2222 10.6667V5.33333C14.2222 4.35556 13.8741 3.51852 13.1778 2.82222C12.4815 2.12593 11.6444 1.77778 10.6667 1.77778H5.33333C4.35556 1.77778 3.51852 2.12593 2.82222 2.82222C2.12593 3.51852 1.77778 4.35556 1.77778 5.33333V10.6667C1.77778 11.6444 2.12593 12.4815 2.82222 13.1778C3.51852 13.8741 4.35556 14.2222 5.33333 14.2222Z" fill="#D22121"/>
|
||||||
|
<rect x="4.81836" y="10.9749" width="8" height="1" rx="0.5" transform="rotate(-45 4.81836 10.9749)" fill="#D22121"/>
|
||||||
|
<rect x="5.52539" y="5.31799" width="8" height="1" rx="0.5" transform="rotate(45 5.52539 5.31799)" fill="#D22121"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1846_342">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
3
assets/icons/crossed_eye.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="9" viewBox="0 0 10 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.53669 8.89177L6.8122 7.15382C6.52317 7.26749 6.22614 7.34957 5.92111 7.40005C5.61615 7.45053 5.3063 7.47578 4.99155 7.47578C3.95998 7.47578 3.01527 7.19683 2.15742 6.63893C1.29964 6.08096 0.619788 5.34562 0.117849 4.43293C0.0770142 4.36144 0.047164 4.28862 0.0282984 4.21447C0.00943278 4.14041 0 4.06506 0 3.98842C0 3.91178 0.0102087 3.8356 0.030626 3.75987C0.0510433 3.68423 0.0816693 3.61224 0.122504 3.5439C0.317857 3.21368 0.525501 2.89417 0.745437 2.58538C0.96529 2.2765 1.22096 2.00328 1.51243 1.76572L0.406101 0.633204C0.338315 0.564287 0.303606 0.478889 0.301972 0.377008C0.300421 0.275127 0.337662 0.185536 0.413696 0.108233C0.484667 0.0360776 0.570746 0 0.671934 0C0.773041 0 0.85908 0.0360776 0.93005 0.108233L9.05304 8.3668C9.12091 8.43579 9.15689 8.52119 9.16097 8.62299C9.16505 8.72487 9.12908 8.81446 9.05304 8.89177C8.98207 8.96392 8.89599 9 8.79481 9C8.6937 9 8.60766 8.96392 8.53669 8.89177ZM4.99155 6.01955C5.10025 6.01955 5.20405 6.01299 5.30295 5.99988C5.40194 5.98676 5.49855 5.95815 5.5928 5.91406L3.09751 3.37713C3.05725 3.47295 3.02989 3.57118 3.01544 3.67181C3.00098 3.77237 2.99375 3.8779 2.99375 3.98842C2.99375 4.5537 3.18772 5.03355 3.57565 5.42795C3.96358 5.82235 4.43554 6.01955 4.99155 6.01955ZM4.99155 0.501058C6.02622 0.501058 6.97529 0.781293 7.83878 1.34176C8.70227 1.90223 9.38344 2.64201 9.88227 3.56109C9.91992 3.62436 9.94896 3.69224 9.96937 3.76473C9.98979 3.83721 10 3.91178 10 3.98842C10 4.06506 9.99106 4.13962 9.97317 4.21211C9.95529 4.28459 9.92748 4.35247 9.88975 4.41574C9.74086 4.70378 9.57442 4.97521 9.39042 5.23004C9.20634 5.48487 9.00057 5.72471 8.77312 5.94956C8.67136 6.05302 8.55074 6.10059 8.41125 6.09229C8.27184 6.08399 8.15121 6.02811 8.04937 5.92465L7.0223 4.88517C6.97142 4.83344 6.93781 4.77369 6.92147 4.70594C6.90514 4.63827 6.90608 4.56769 6.92429 4.49421C6.94749 4.40868 6.96411 4.32565 6.97415 4.24511C6.98428 4.16465 6.98934 4.07909 6.98934 3.98842C6.98934 3.42313 6.79538 2.94329 6.40745 2.54888C6.01952 2.15448 5.54755 1.95728 4.99155 1.95728C4.90236 1.95728 4.81898 1.96322 4.74139 1.97509C4.66381 1.98688 4.58447 2.00461 4.50337 2.02827C4.43117 2.04994 4.35894 2.05248 4.28666 2.03587C4.21446 2.01926 4.15293 1.9851 4.10205 1.93337L3.64584 1.46967C3.50643 1.32785 3.46012 1.16274 3.50692 0.974343C3.55372 0.785942 3.67136 0.665878 3.85985 0.614149C4.04516 0.575788 4.23174 0.547349 4.41958 0.528832C4.60742 0.510316 4.79807 0.501058 4.99155 0.501058ZM5.94046 3.04758C6.03977 3.15876 6.1216 3.28347 6.18596 3.42172C6.25032 3.55997 6.29066 3.70449 6.30699 3.85527C6.31516 3.91531 6.29303 3.95699 6.2406 3.98032C6.18817 4.00357 6.139 3.9919 6.0931 3.94532L5.04153 2.88093C4.99563 2.83427 4.98542 2.78428 5.0109 2.73097C5.0363 2.67767 5.07852 2.65101 5.13757 2.65101C5.29593 2.66438 5.44199 2.70478 5.57577 2.7722C5.70962 2.83954 5.83119 2.93133 5.94046 3.04758Z" fill="#2F80ED"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
3
assets/icons/sucess.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.33333 16C3.85185 16 2.59259 15.4815 1.55556 14.4444C0.518519 13.4074 0 12.1481 0 10.6667V5.33333C0 3.85185 0.518519 2.59259 1.55556 1.55556C2.59259 0.518519 3.85185 0 5.33333 0H10.6667C12.1481 0 13.4074 0.518519 14.4444 1.55556C15.4815 2.59259 16 3.85185 16 5.33333V10.6667C16 12.1481 15.4815 13.4074 14.4444 14.4444C13.4074 15.4815 12.1481 16 10.6667 16H5.33333ZM7.11111 9.06667L5.77778 7.73333C5.61481 7.57037 5.40741 7.48889 5.15556 7.48889C4.9037 7.48889 4.6963 7.57037 4.53333 7.73333C4.37037 7.8963 4.28889 8.1037 4.28889 8.35556C4.28889 8.60741 4.37037 8.81481 4.53333 8.97778L6.48889 10.9333C6.66667 11.1111 6.87407 11.2 7.11111 11.2C7.34815 11.2 7.55556 11.1111 7.73333 10.9333L11.8222 6.84444C11.9852 6.68148 12.0667 6.47407 12.0667 6.22222C12.0667 5.97037 11.9852 5.76296 11.8222 5.6C11.6593 5.43704 11.4519 5.35556 11.2 5.35556C10.9481 5.35556 10.7407 5.43704 10.5778 5.6L7.11111 9.06667ZM5.33333 14.2222H10.6667C11.6444 14.2222 12.4815 13.8741 13.1778 13.1778C13.8741 12.4815 14.2222 11.6444 14.2222 10.6667V5.33333C14.2222 4.35556 13.8741 3.51852 13.1778 2.82222C12.4815 2.12593 11.6444 1.77778 10.6667 1.77778H5.33333C4.35556 1.77778 3.51852 2.12593 2.82222 2.82222C2.12593 3.51852 1.77778 4.35556 1.77778 5.33333V10.6667C1.77778 11.6444 2.12593 12.4815 2.82222 13.1778C3.51852 13.8741 4.35556 14.2222 5.33333 14.2222Z" fill="#16A249"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/video_call/eatvio_c.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
3
assets/icons/video_call/poor_connection.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 12.0833V7.91666C0 7.56944 0.121528 7.2743 0.364583 7.03125C0.607639 6.78819 0.902778 6.66666 1.25 6.66666C1.59722 6.66666 1.89236 6.78819 2.13542 7.03125C2.37847 7.2743 2.5 7.56944 2.5 7.91666V12.0833C2.5 12.4306 2.37847 12.7257 2.13542 12.9687C1.89236 13.2118 1.59722 13.3333 1.25 13.3333C0.902778 13.3333 0.607639 13.2118 0.364583 12.9687C0.121528 12.7257 0 12.4306 0 12.0833ZM5 12.0833V5.83333C5 5.48611 5.12153 5.19097 5.36458 4.94791C5.60764 4.70486 5.90278 4.58333 6.25 4.58333C6.59722 4.58333 6.89236 4.70486 7.13542 4.94791C7.37847 5.19097 7.5 5.48611 7.5 5.83333V12.0833C7.5 12.4306 7.37847 12.7257 7.13542 12.9687C6.89236 13.2118 6.59722 13.3333 6.25 13.3333C5.90278 13.3333 5.60764 13.2118 5.36458 12.9687C5.12153 12.7257 5 12.4306 5 12.0833ZM10 12.0833V3.75C10 3.40278 10.1215 3.10764 10.3646 2.86458C10.6076 2.62153 10.9028 2.5 11.25 2.5C11.5972 2.5 11.8924 2.62153 12.1354 2.86458C12.3785 3.10764 12.5 3.40278 12.5 3.75V12.0833C12.5 12.4306 12.3785 12.7257 12.1354 12.9687C11.8924 13.2118 11.5972 13.3333 11.25 13.3333C10.9028 13.3333 10.6076 13.2118 10.3646 12.9687C10.1215 12.7257 10 12.4306 10 12.0833ZM15 6.8125V1.25C15 0.902777 15.1215 0.607639 15.3646 0.364583C15.6076 0.121528 15.9028 0 16.25 0C16.5972 0 16.8924 0.121528 17.1354 0.364583C17.3785 0.607639 17.5 0.902777 17.5 1.25V6.54166C17.3611 6.51389 17.2257 6.49305 17.0938 6.47916C16.9618 6.46528 16.8194 6.45833 16.6667 6.45833C16.375 6.45833 16.0868 6.48958 15.8021 6.55208C15.5174 6.61458 15.25 6.70139 15 6.8125ZM16.0729 13.0937C15.9132 12.934 15.8333 12.7361 15.8333 12.5C15.8333 12.2639 15.9132 12.066 16.0729 11.9062C16.2326 11.7465 16.4306 11.6667 16.6667 11.6667C16.9028 11.6667 17.1007 11.7465 17.2604 11.9062C17.4201 12.066 17.5 12.2639 17.5 12.5C17.5 12.7361 17.4201 12.934 17.2604 13.0937C17.1007 13.2535 16.9028 13.3333 16.6667 13.3333C16.4306 13.3333 16.2326 13.2535 16.0729 13.0937ZM15.8333 10V8.75C15.8333 8.51389 15.9132 8.31597 16.0729 8.15625C16.2326 7.99652 16.4306 7.91666 16.6667 7.91666C16.9028 7.91666 17.1007 7.99652 17.2604 8.15625C17.4201 8.31597 17.5 8.51389 17.5 8.75V10C17.5 10.2361 17.4201 10.434 17.2604 10.5937C17.1007 10.7535 16.9028 10.8333 16.6667 10.8333C16.4306 10.8333 16.2326 10.7535 16.0729 10.5937C15.9132 10.434 15.8333 10.2361 15.8333 10Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
3
assets/icons/video_call/vc_cam_off.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.292969 2.29289C0.683493 1.90237 1.31651 1.90237 1.70703 2.29289L3.41406 3.99992H11.4424C12.3865 3.99991 13.1558 3.99969 13.7803 4.05071C14.4257 4.10345 15.0042 4.21564 15.543 4.49016C16.3897 4.92159 17.0783 5.61023 17.5098 6.45696C17.7843 6.99573 17.8965 7.57424 17.9492 8.21965C17.9518 8.25175 17.9517 8.28444 17.9541 8.31731L22.4189 5.12981C23.0808 4.65704 24 5.12991 24 5.94328V18.0566C24 18.8699 23.0808 19.3428 22.4189 18.87L18 15.7138V18.5859L19.707 20.2929C20.0976 20.6834 20.0976 21.3164 19.707 21.707C19.3165 22.0975 18.6835 22.0975 18.293 21.707L0.292969 3.70696C-0.097555 3.31643 -0.0975551 2.68342 0.292969 2.29289ZM1 7.99895C1.55212 7.99895 1.99974 8.44689 2 8.99895V13.3993C2 14.3959 2.00036 15.0836 2.04395 15.6171C2.08658 16.1387 2.16554 16.4249 2.27246 16.6347C2.51215 17.105 2.89491 17.4868 3.36523 17.7265C3.57511 17.8334 3.86102 17.9124 4.38281 17.955C4.91619 17.9986 5.60333 17.9989 6.59961 17.9989H11C11.5521 17.9989 11.9997 18.4469 12 18.9989C12 19.5512 11.5523 19.9989 11 19.9989H6.55762C5.61342 19.999 4.84421 19.9992 4.21973 19.9482C3.57428 19.8954 2.99583 19.7832 2.45703 19.5087C1.61035 19.0773 0.921646 18.3886 0.490235 17.5419C0.215871 17.0032 0.103504 16.4245 0.0507818 15.7792C-0.000207456 15.1548 -1.10191e-05 14.3854 5.0477e-07 13.4413V8.99895C0.00026378 8.44689 0.447878 7.99895 1 7.99895ZM16 16.5859V10.5995C16 9.60324 15.9986 8.91611 15.9551 8.38274C15.9125 7.86113 15.8344 7.57502 15.7275 7.36516C15.4879 6.89475 15.1052 6.51207 14.6348 6.27239C14.4249 6.16547 14.1389 6.0865 13.6172 6.04387C13.0837 6.00029 12.396 5.99992 11.3994 5.99992H5.41406L16 16.5859Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/icons/video_call/vc_camera_switch.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/icons/video_call/vc_control_chat.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/icons/video_call/vc_control_leave.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
assets/icons/video_call/vc_control_mic.png
Normal file
|
After Width: | Height: | Size: 694 B |
BIN
assets/icons/video_call/vc_control_mic_off.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
assets/icons/video_call/vc_control_sound_active.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/icons/video_call/vc_control_video.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/icons/video_call/vc_expand.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
3
assets/icons/video_call/vc_expand.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.83398 9.03418C6.1464 8.72177 6.6534 8.72178 6.96582 9.03418C7.27824 9.3466 7.27824 9.8536 6.96582 10.166L2.73047 14.4004H5.59961C6.04144 14.4004 6.40039 14.7584 6.40039 15.2002C6.40032 15.642 6.04139 16 5.59961 16H0.799805C0.750353 16 0.702243 15.994 0.655273 15.9854C0.650431 15.9845 0.645453 15.9844 0.640625 15.9834C0.632394 15.9817 0.624348 15.9795 0.616211 15.9775C0.599845 15.9737 0.583526 15.9697 0.567383 15.9648C0.525709 15.9522 0.485753 15.936 0.447266 15.917C0.370564 15.8792 0.298188 15.8294 0.234375 15.7656C0.170233 15.7015 0.119834 15.6289 0.0820312 15.5518C0.0522722 15.4911 0.0292924 15.4265 0.015625 15.3584C0.0146554 15.3536 0.0145519 15.3486 0.0136719 15.3438C0.0052094 15.2971 7.59671e-06 15.2493 0 15.2002V10.4004C0 9.95863 0.358067 9.59972 0.799805 9.59961C1.24163 9.59961 1.59961 9.95856 1.59961 10.4004V13.2686L5.83398 9.03418ZM15.2002 0C15.2493 1.2747e-05 15.2971 0.0052045 15.3438 0.0136719C15.3486 0.0145519 15.3536 0.0146554 15.3584 0.015625C15.3666 0.017277 15.3747 0.0195822 15.3828 0.0214844C15.3992 0.0253083 15.4155 0.0293148 15.4316 0.0341797C15.4733 0.0467652 15.5133 0.0631386 15.5518 0.0820312C15.6289 0.119834 15.7015 0.170233 15.7656 0.234375C15.8294 0.298188 15.8792 0.370564 15.917 0.447266C15.9252 0.463964 15.9334 0.480716 15.9404 0.498047C15.9612 0.549032 15.9755 0.601773 15.9854 0.655273C15.994 0.702239 16 0.750358 16 0.799805V5.59961C16 6.04137 15.6419 6.40028 15.2002 6.40039C14.7584 6.40039 14.4004 6.04144 14.4004 5.59961V2.73047L10.166 6.96582C9.8536 7.27824 9.3466 7.27824 9.03418 6.96582C8.72178 6.6534 8.72177 6.1464 9.03418 5.83398L13.2695 1.59961H10.4004C9.95856 1.59961 9.59961 1.24163 9.59961 0.799805C9.59972 0.358067 9.95863 0 10.4004 0H15.2002Z" fill="#EFF0FA"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
9
assets/icons/video_call/vc_mic_off.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16 6.3V6C16 3.79 14.21 2 12 2C9.79 2 8 3.79 8 6V11" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9.04004 14.19C9.77004 15 10.83 15.5 12 15.5C14.21 15.5 16 13.71 16 11.5V11" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.77979 16.9499C8.14979 18.2199 9.97978 18.9999 11.9998 18.9999C16.2198 18.9999 19.6498 15.5699 19.6498 11.3499V9.6499" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.3501 9.6499V11.3499C4.3501 12.4099 4.5601 13.4099 4.9501 14.3299" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20.0702 2.83984L3.93018 18.9898" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 3V6" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 19V22" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
5
assets/icons/video_call/vc_mic_on.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 15.5C14.21 15.5 16 13.71 16 11.5V6C16 3.79 14.21 2 12 2C9.79 2 8 3.79 8 6V11.5C8 13.71 9.79 15.5 12 15.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.35059 9.6499V11.3499C4.35059 15.5699 7.78059 18.9999 12.0006 18.9999C16.2206 18.9999 19.6506 15.5699 19.6506 11.3499V9.6499" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 19V22" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 629 B |
3
assets/icons/video_call/vc_shrink.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.40023 8.79965C6.44894 8.79967 6.49651 8.80496 6.54281 8.81332C6.54796 8.81425 6.5533 8.81424 6.55844 8.81527C6.56666 8.81692 6.57472 8.81923 6.58285 8.82113C6.5992 8.82496 6.61555 8.82896 6.63168 8.83383C6.64056 8.83651 6.64929 8.83963 6.65805 8.84262C6.69881 8.85648 6.73865 8.87374 6.77719 8.89437C6.80436 8.90894 6.83012 8.9257 6.85531 8.9432C6.89387 8.97001 6.93129 8.99965 6.96566 9.03402C6.9997 9.06805 7.02889 9.10526 7.05551 9.1434C7.05897 9.14836 7.06289 9.153 7.06625 9.15804C7.12722 9.24979 7.16631 9.35128 7.18539 9.4559C7.19389 9.50255 7.20001 9.55035 7.20004 9.59945V14.4002C7.19984 14.8418 6.84178 15.1998 6.40023 15.2C5.95852 15.2 5.59964 14.8419 5.59945 14.4002V11.5311L1.36508 15.7655C1.05264 16.0777 0.546577 16.0778 0.234217 15.7655C-0.0781257 15.4531 -0.0780189 14.947 0.234217 14.6346L4.46762 10.4002H1.59945C1.15782 10.4 0.799647 10.0411 0.799647 9.59945C0.799876 9.15796 1.15796 8.79988 1.59945 8.79965H6.40023ZM14.6346 0.234217C14.947 -0.0780189 15.4531 -0.0781257 15.7655 0.234217C16.0778 0.546577 16.0777 1.05264 15.7655 1.36508L11.5321 5.59945H14.4002C14.8419 5.59964 15.2 5.95852 15.2 6.40023C15.1998 6.84178 14.8418 7.19984 14.4002 7.20004H9.59945C9.55035 7.20001 9.50255 7.19389 9.4559 7.18539C9.45074 7.18445 9.44541 7.18448 9.44027 7.18344C9.43204 7.18177 9.42399 7.17949 9.41586 7.17758C9.3995 7.17372 9.38317 7.16978 9.36703 7.16488C9.35943 7.16257 9.35207 7.1596 9.34457 7.15707C9.30221 7.14282 9.2605 7.12587 9.22054 7.10433C9.19365 7.08982 9.16833 7.0729 9.1434 7.05551C9.10526 7.02889 9.06805 6.9997 9.03402 6.96566C8.99965 6.93129 8.97001 6.89387 8.9432 6.85531C8.9257 6.83012 8.90894 6.80436 8.89437 6.77719C8.85465 6.70298 8.82796 6.62378 8.81332 6.54281C8.80496 6.49651 8.79967 6.44894 8.79965 6.40023V1.59945C8.79988 1.15796 9.15796 0.799876 9.59945 0.799647C10.0411 0.799647 10.4 1.15782 10.4002 1.59945V4.46762L14.6346 0.234217Z" fill="#EFF0FA"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
3
assets/icons/video_call/warning.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 0C15.523 0 20 4.477 20 10C20 15.523 15.523 20 10 20C4.477 20 0 15.523 0 10C0 4.477 4.477 0 10 0ZM10 2C7.87827 2 5.84344 2.84285 4.34315 4.34315C2.84285 5.84344 2 7.87827 2 10C2 12.1217 2.84285 14.1566 4.34315 15.6569C5.84344 17.1571 7.87827 18 10 18C12.1217 18 14.1566 17.1571 15.6569 15.6569C17.1571 14.1566 18 12.1217 18 10C18 7.87827 17.1571 5.84344 15.6569 4.34315C14.1566 2.84285 12.1217 2 10 2ZM10 13C10.2652 13 10.5196 13.1054 10.7071 13.2929C10.8946 13.4804 11 13.7348 11 14C11 14.2652 10.8946 14.5196 10.7071 14.7071C10.5196 14.8946 10.2652 15 10 15C9.73478 15 9.48043 14.8946 9.29289 14.7071C9.10536 14.5196 9 14.2652 9 14C9 13.7348 9.10536 13.4804 9.29289 13.2929C9.48043 13.1054 9.73478 13 10 13ZM10 4C10.2652 4 10.5196 4.10536 10.7071 4.29289C10.8946 4.48043 11 4.73478 11 5V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V5C9 4.73478 9.10536 4.48043 9.29289 4.29289C9.48043 4.10536 9.73478 4 10 4Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
28716
coverage/lcov.info
@@ -388,7 +388,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 81;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -539,7 +539,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 81;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -578,7 +578,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 81;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>82</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
<string>This permission is not needed by the app, but it is required by an underlying API. If you see this dialog, contact us.</string>
|
<string>This permission is not needed by the app, but it is required by an underlying API. If you see this dialog, contact us.</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
|
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Нужен для звука в видеозвонках с консультантом</string>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
@@ -72,6 +74,57 @@
|
|||||||
<string>yandexauth</string>
|
<string>yandexauth</string>
|
||||||
<string>yandexauth2</string>
|
<string>yandexauth2</string>
|
||||||
<string>yandexauth4</string>
|
<string>yandexauth4</string>
|
||||||
|
<!-- SBP bank schemes (top ~50 by popularity, iOS 15+ caps at 50) -->
|
||||||
|
<string>bank100000000111</string>
|
||||||
|
<string>bank100000000004</string>
|
||||||
|
<string>bank100000000005</string>
|
||||||
|
<string>bank100000000008</string>
|
||||||
|
<string>bank100000000007</string>
|
||||||
|
<string>bank100000000015</string>
|
||||||
|
<string>bank100000000001</string>
|
||||||
|
<string>bank100000000010</string>
|
||||||
|
<string>bank100000000013</string>
|
||||||
|
<string>bank100000000012</string>
|
||||||
|
<string>bank100000000020</string>
|
||||||
|
<string>bank100000000025</string>
|
||||||
|
<string>bank100000000030</string>
|
||||||
|
<string>bank100000000030_new</string>
|
||||||
|
<string>bank100000000003</string>
|
||||||
|
<string>bank100000000043</string>
|
||||||
|
<string>bank100000000028</string>
|
||||||
|
<string>bank100000000086</string>
|
||||||
|
<string>bank100000000011</string>
|
||||||
|
<string>bank100000000044</string>
|
||||||
|
<string>bank100000000049</string>
|
||||||
|
<string>bank100000000500</string>
|
||||||
|
<string>bank100000000095</string>
|
||||||
|
<string>bank100000000900</string>
|
||||||
|
<string>bank100000000056</string>
|
||||||
|
<string>bank100000000053</string>
|
||||||
|
<string>bank100000000121</string>
|
||||||
|
<string>bank100000000082</string>
|
||||||
|
<string>bank100000000127</string>
|
||||||
|
<string>bank100000000017</string>
|
||||||
|
<string>bank100000000087</string>
|
||||||
|
<string>bank100000000052</string>
|
||||||
|
<string>bank100000000006</string>
|
||||||
|
<string>bank100000000098</string>
|
||||||
|
<string>bank100000000092</string>
|
||||||
|
<string>bank100000000229</string>
|
||||||
|
<string>bank100000000027</string>
|
||||||
|
<string>bank100000000080</string>
|
||||||
|
<string>bank100000000122</string>
|
||||||
|
<string>bank100000000124</string>
|
||||||
|
<string>bank100000000118</string>
|
||||||
|
<string>bank100000000159</string>
|
||||||
|
<string>bank100000000146</string>
|
||||||
|
<string>bank100000000069</string>
|
||||||
|
<string>bank100000000140</string>
|
||||||
|
<string>bank100000000047</string>
|
||||||
|
<string>bank100000000099</string>
|
||||||
|
<string>bank100000000135</string>
|
||||||
|
<string>bank100000000139</string>
|
||||||
|
<string>bank100000000022</string>
|
||||||
</array>
|
</array>
|
||||||
<key>YAClientId</key>
|
<key>YAClientId</key>
|
||||||
<string>5399b03636104c6d920b87c1ad329254</string>
|
<string>5399b03636104c6d920b87c1ad329254</string>
|
||||||
|
|||||||
@@ -48,8 +48,33 @@ platform :ios do
|
|||||||
UI.message "Configuring Flutter for release mode..."
|
UI.message "Configuring Flutter for release mode..."
|
||||||
sh("cd .. && flutter build ios --config-only --release")
|
sh("cd .. && flutter build ios --config-only --release")
|
||||||
|
|
||||||
|
# Read build number from Flutter Generated.xcconfig
|
||||||
|
generated_xcconfig_path = File.join("..", "Flutter", "Generated.xcconfig")
|
||||||
|
flutter_build_number = nil
|
||||||
|
|
||||||
|
if File.exist?(generated_xcconfig_path)
|
||||||
|
File.foreach(generated_xcconfig_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_BUILD_NUMBER=(.*)/)
|
||||||
|
if matches
|
||||||
|
flutter_build_number = matches[1].strip
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if flutter_build_number.nil?
|
||||||
|
UI.user_error!("Could not find FLUTTER_BUILD_NUMBER in #{generated_xcconfig_path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
UI.message "Flutter build number: #{flutter_build_number}"
|
||||||
|
|
||||||
match(type: "appstore", readonly: true)
|
match(type: "appstore", readonly: true)
|
||||||
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
|
||||||
|
# Set build number explicitly instead of incrementing
|
||||||
|
increment_build_number(
|
||||||
|
xcodeproj: "Runner.xcodeproj",
|
||||||
|
build_number: flutter_build_number
|
||||||
|
)
|
||||||
|
|
||||||
build_app(
|
build_app(
|
||||||
workspace: "Runner.xcworkspace",
|
workspace: "Runner.xcworkspace",
|
||||||
@@ -74,7 +99,7 @@ platform :ios do
|
|||||||
skip_submission: true,
|
skip_submission: true,
|
||||||
distribute_external: false,
|
distribute_external: false,
|
||||||
notify_external_testers: false,
|
notify_external_testers: false,
|
||||||
changelog: "TestFlight build #{get_build_number(xcodeproj: 'Runner.xcodeproj')}",
|
changelog: "TestFlight build #{flutter_build_number}",
|
||||||
verbose: true
|
verbose: true
|
||||||
)
|
)
|
||||||
rescue => ex
|
rescue => ex
|
||||||
|
|||||||
@@ -33,15 +33,16 @@ class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Список глобальных BlocProvider'ов
|
/// Список глобальных BlocProvider'ов
|
||||||
|
/// Используются lazy: true для отложенной инициализации, чтобы не блокировать старт приложения
|
||||||
List<BlocProvider> get globalBlocProviders => [
|
List<BlocProvider> get globalBlocProviders => [
|
||||||
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
|
BlocProvider<NewMessagesCubit>(lazy: true, create: (context) => sl<NewMessagesCubit>()),
|
||||||
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()..getNotifications()),
|
BlocProvider<NotificationsCubit>(lazy: true, create: (context) => sl<NotificationsCubit>()),
|
||||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
BlocProvider<FeedCubit>(lazy: true, create: (context) => sl<FeedCubit>()),
|
||||||
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
|
BlocProvider<ProfileAvatarCubit>(lazy: true, create: (context) => sl<ProfileAvatarCubit>()),
|
||||||
BlocProvider<ChatsCubit>(create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading()),
|
BlocProvider<ChatsCubit>(lazy: true, create: (context) => sl<ChatsCubit>()),
|
||||||
BlocProvider<ViewsCubit>(create: (context) => sl<ViewsCubit>()),
|
BlocProvider<ViewsCubit>(lazy: true, create: (context) => sl<ViewsCubit>()),
|
||||||
BlocProvider<InternetConnectionCubit>(lazy: false, create: (context) => sl<InternetConnectionCubit>()),
|
BlocProvider<InternetConnectionCubit>(lazy: false, create: (context) => sl<InternetConnectionCubit>()),
|
||||||
BlocProvider<MarketingCubit>(create: (context) => sl<MarketingCubit>()),
|
BlocProvider<MarketingCubit>(lazy: true, create: (context) => sl<MarketingCubit>()),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Делегаты локализации
|
/// Делегаты локализации
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/env_config.dart';
|
import 'package:eatvio_app/core/env_config.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart' as di;
|
import 'package:eatvio_app/core/locator_service.dart' as di;
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/firebase_options.dart';
|
import 'package:eatvio_app/firebase_options.dart';
|
||||||
@@ -14,6 +17,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||||||
|
|
||||||
/// Класс для инициализации приложения
|
/// Класс для инициализации приложения
|
||||||
class AppInitializer {
|
class AppInitializer {
|
||||||
|
|
||||||
/// Инициализация всех сервисов приложения
|
/// Инициализация всех сервисов приложения
|
||||||
static Future<void> initialize({required AppBuildType buildType}) async {
|
static Future<void> initialize({required AppBuildType buildType}) async {
|
||||||
await _initializeEnvironment();
|
await _initializeEnvironment();
|
||||||
@@ -37,9 +41,19 @@ class AppInitializer {
|
|||||||
|
|
||||||
/// Инициализация Firebase
|
/// Инициализация Firebase
|
||||||
static Future<void> _initializeFirebase() async {
|
static Future<void> _initializeFirebase() async {
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
onTimeout: () {
|
||||||
|
throw TimeoutException('Firebase initialization timeout');
|
||||||
|
},
|
||||||
|
);
|
||||||
if (sl<AppConfig>().buildType == AppBuildType.prod) {
|
if (sl<AppConfig>().buildType == AppBuildType.prod) {
|
||||||
await FirebaseMessaging.instance.requestPermission();
|
try {
|
||||||
|
await FirebaseMessaging.instance.requestPermission().timeout(const Duration(seconds: 5));
|
||||||
|
} catch (e) {
|
||||||
|
// Разрешение не критично, продолжаем работу
|
||||||
|
log('Failed to request Firebase messaging permission: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +81,11 @@ class AppInitializer {
|
|||||||
options.dsn = EnvConfig.sentryDsn;
|
options.dsn = EnvConfig.sentryDsn;
|
||||||
options.tracesSampleRate = 1.0;
|
options.tracesSampleRate = 1.0;
|
||||||
options.debug = false;
|
options.debug = false;
|
||||||
|
options.beforeSend = (event, hint) {
|
||||||
|
if (kDebugMode) return null;
|
||||||
|
if (!ErrorReportPolicy.allowSentryEvent(event, hint)) return null;
|
||||||
|
return event;
|
||||||
|
};
|
||||||
}, appRunner: appRunner);
|
}, appRunner: appRunner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer' as developer;
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/routing/auth_routes.dart';
|
import 'package:eatvio_app/core/routing/auth_routes.dart';
|
||||||
import 'package:eatvio_app/core/routing/content_routes.dart';
|
import 'package:eatvio_app/core/routing/content_routes.dart';
|
||||||
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
|
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
|
||||||
@@ -5,6 +7,7 @@ import 'package:eatvio_app/core/routing/other_routes.dart';
|
|||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/routing/settings_routes.dart';
|
import 'package:eatvio_app/core/routing/settings_routes.dart';
|
||||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||||
|
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -13,6 +16,9 @@ import 'package:go_router/go_router.dart';
|
|||||||
class AppRouter {
|
class AppRouter {
|
||||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com'];
|
||||||
|
static const List<String> metaSegments = ['ru', 'en', 'api', 'v2'];
|
||||||
|
|
||||||
static GoRouter appRouter = GoRouter(
|
static GoRouter appRouter = GoRouter(
|
||||||
initialLocation: RouteConstants.updateScreen,
|
initialLocation: RouteConstants.updateScreen,
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
@@ -26,52 +32,230 @@ class AppRouter {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Обработка редиректов для deep links
|
static String? _resolveRecipeDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-recipes-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveBlogPostDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/blog-post-screen?id=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolvePlanDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
final planId = int.tryParse(contentIdentifier);
|
||||||
|
if (planId != null) {
|
||||||
|
developer.log('Deep link: diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-plan-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveDietDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
final dietId = int.tryParse(contentIdentifier);
|
||||||
|
if (dietId != null) {
|
||||||
|
developer.log('Deep link: diet/id=$dietId', name: 'AppRouter');
|
||||||
|
return '/about-diet-screen?id=$dietId';
|
||||||
|
}
|
||||||
|
developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/about-diet-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
const dietTabIndexWithHiddenPlansTab = 3;
|
||||||
|
const dietTabIndexWithoutHiddenPlansTab = 4;
|
||||||
|
|
||||||
|
final initialTabIndex = hidePlans ? dietTabIndexWithHiddenPlansTab : dietTabIndexWithoutHiddenPlansTab;
|
||||||
|
return '${RouteConstants.feedScreen}?initialTabIndex=$initialTabIndex';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveChatDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
|
||||||
|
} else if (effectiveSegments.length == 3 && effectiveSegments[1] == 'show') {
|
||||||
|
final contentIdentifier = effectiveSegments[2];
|
||||||
|
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('Deep link: chat', name: 'AppRouter');
|
||||||
|
return RouteConstants.chatSelectScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveProfileDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length == 2) {
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
|
||||||
|
return '/profile-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
return RouteConstants.myProfileScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveBadgeDeepLink(List<String> effectiveSegments) {
|
||||||
|
return RouteConstants.myProfileScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveDiaryDeepLink(List<String> effectiveSegments) {
|
||||||
|
developer.log('Deep link: diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveConsultantDiaryCommentDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 2) return null;
|
||||||
|
final contentIdentifier = effectiveSegments[1];
|
||||||
|
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
|
||||||
|
return RouteConstants.diaryScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _resolveConsultationsDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 3 || effectiveSegments[1] != 'status') return RouteConstants.consultationsScreen;
|
||||||
|
final consultationId = effectiveSegments[2];
|
||||||
|
developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter');
|
||||||
|
return '${InfoByOrderScreen.path}?planId=$consultationId';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deep link после оплаты Alfa: `finance/alfa/return/{orderId}` → чат с query `orderId`.
|
||||||
|
static String? _resolveFinanceAlfaReturnDeepLink(List<String> effectiveSegments) {
|
||||||
|
if (effectiveSegments.length != 4 ||
|
||||||
|
effectiveSegments[1] != 'alfa' ||
|
||||||
|
effectiveSegments[2] != 'return') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final orderId = effectiveSegments[3];
|
||||||
|
if (orderId.isEmpty) return null;
|
||||||
|
developer.log('Deep link: finance/alfa/return/$orderId', name: 'AppRouter');
|
||||||
|
return '${RouteConstants.chatScreen}?orderId=$orderId';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Разбирает deep link URI в внутренний route target (или null, если host не наш).
|
||||||
|
static String? resolveDeepLinkTarget(Uri uri) {
|
||||||
|
final host = uri.host.toLowerCase();
|
||||||
|
final segments = uri.pathSegments;
|
||||||
|
|
||||||
|
final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost'));
|
||||||
|
|
||||||
|
if (!isDeepLink) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('Deep link detected: host=$host, path=${uri.path}, segments=$segments', name: 'AppRouter');
|
||||||
|
|
||||||
|
if (segments.isEmpty) {
|
||||||
|
developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter');
|
||||||
|
return RouteConstants.feedScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList();
|
||||||
|
if (effectiveSegments.isEmpty) {
|
||||||
|
developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter');
|
||||||
|
return RouteConstants.feedScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
final type = effectiveSegments[0];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'recipe':
|
||||||
|
final target = _resolveRecipeDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'blog_post':
|
||||||
|
final target = _resolveBlogPostDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'plan':
|
||||||
|
final target = _resolvePlanDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'diet':
|
||||||
|
final target = _resolveDietDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'chat':
|
||||||
|
final target = _resolveChatDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'profile':
|
||||||
|
final target = _resolveProfileDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'badge':
|
||||||
|
final target = _resolveBadgeDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'diary':
|
||||||
|
final target = _resolveDiaryDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'consultant_diary_comment':
|
||||||
|
final target = _resolveConsultantDiaryCommentDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'consultations':
|
||||||
|
final target = _resolveConsultationsDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
case 'finance':
|
||||||
|
final target = _resolveFinanceAlfaReturnDeepLink(effectiveSegments);
|
||||||
|
if (target != null) return target;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter');
|
||||||
|
return RouteConstants.feedScreen;
|
||||||
|
}
|
||||||
|
|
||||||
static String? _handleRedirect(BuildContext context, GoRouterState state) {
|
static String? _handleRedirect(BuildContext context, GoRouterState state) {
|
||||||
final segments = state.uri.pathSegments;
|
return resolveDeepLinkTarget(state.uri);
|
||||||
if (segments.length == 4) {
|
}
|
||||||
final type = segments[1];
|
|
||||||
switch (type) {
|
|
||||||
case 'diary':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/diary-screen';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (segments.length == 3) {
|
|
||||||
final type = segments[1];
|
|
||||||
final contentIdentifier = segments[2];
|
|
||||||
|
|
||||||
switch (type) {
|
static void navigateByDeepLinkUrl(String url) {
|
||||||
case 'recipe':
|
if (url.isEmpty) {
|
||||||
FlutterNativeSplash.remove();
|
developer.log('navigateByDeepLinkUrl: empty url, redirecting to feed', name: 'AppRouter');
|
||||||
return '/about-recipes-screen?slug=$contentIdentifier';
|
appRouter.go(RouteConstants.feedScreen);
|
||||||
case 'blog_post':
|
return;
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/blog-post-screen?id=$contentIdentifier';
|
|
||||||
case 'plan':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/about-plan-screen?slug=$contentIdentifier';
|
|
||||||
case 'diet':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/about-diet-screen?slug=$contentIdentifier';
|
|
||||||
case 'chat':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/chat-select-screen';
|
|
||||||
case 'profile':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/profile-screen?slug=$contentIdentifier';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (segments.length == 2) {
|
|
||||||
final type = segments[1];
|
|
||||||
switch (type) {
|
|
||||||
case 'chat':
|
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return '/chat-select-screen';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
Uri? uri;
|
||||||
|
try {
|
||||||
|
uri = Uri.tryParse(url);
|
||||||
|
} catch (e) {
|
||||||
|
developer.log('navigateByDeepLinkUrl: failed to parse url=$url, error=$e, redirecting to feed', name: 'AppRouter');
|
||||||
|
appRouter.go(RouteConstants.feedScreen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
developer.log('navigateByDeepLinkUrl: uri is null for url=$url, redirecting to feed', name: 'AppRouter');
|
||||||
|
appRouter.go(RouteConstants.feedScreen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) {
|
||||||
|
uri = Uri(
|
||||||
|
scheme: 'https',
|
||||||
|
host: deepLinkHosts.first,
|
||||||
|
pathSegments: uri.pathSegments,
|
||||||
|
queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final target = resolveDeepLinkTarget(uri);
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
if (target != null) {
|
||||||
|
appRouter.go(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter');
|
||||||
|
appRouter.go(RouteConstants.feedScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
|
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
|
||||||
|
|||||||
@@ -5,15 +5,13 @@ import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
|
|||||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class CommentsCubit extends Cubit<CommentsState> {
|
class CommentsCubit extends Cubit<CommentsState> {
|
||||||
final FeedRepository feedRepository;
|
final FeedRepository feedRepository;
|
||||||
final CommentsRepository commentsRepository;
|
final CommentsRepository commentsRepository;
|
||||||
|
|
||||||
CommentsCubit({
|
CommentsCubit({required this.feedRepository, required this.commentsRepository}) : super(const CommentsState());
|
||||||
required this.feedRepository,
|
|
||||||
required this.commentsRepository,
|
|
||||||
}) : super(const CommentsState());
|
|
||||||
|
|
||||||
void setReplyId(int? replyId) {
|
void setReplyId(int? replyId) {
|
||||||
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
||||||
@@ -36,51 +34,34 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
|
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.error(errorMessage: e.toString()));
|
emit(CommentsState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendComment({
|
Future<bool> sendComment({required String id, required String commentText, required CommentSourceType commentSourceType, int? parentCommentId}) async {
|
||||||
required String id,
|
|
||||||
required String commentText,
|
|
||||||
required CommentSourceType commentSourceType,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
switch (commentSourceType) {
|
switch (commentSourceType) {
|
||||||
case CommentSourceType.blogPost:
|
case CommentSourceType.blogPost:
|
||||||
await feedRepository.sendBlogPostComment(
|
await feedRepository.sendBlogPostComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.diet:
|
case CommentSourceType.diet:
|
||||||
await feedRepository.sendDietsComment(
|
await feedRepository.sendDietsComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.recipe:
|
case CommentSourceType.recipe:
|
||||||
await feedRepository.sendRecipesComment(
|
await feedRepository.sendRecipesComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case CommentSourceType.plan:
|
case CommentSourceType.plan:
|
||||||
await feedRepository.sendPlansComment(
|
await feedRepository.sendPlansComment(slug: id, commentText: commentText, parentCommentId: parentCommentId);
|
||||||
slug: id,
|
|
||||||
commentText: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
getComments(id, commentSourceType);
|
getComments(id, commentSourceType);
|
||||||
} catch (e) {
|
return true;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.error(errorMessage: e.toString()));
|
emit(CommentsState.error(errorMessage: e.toString()));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +74,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(CommentsState.loaded(comment: updatedCommentModel));
|
emit(CommentsState.loaded(comment: updatedCommentModel));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.loaded(comment: commentModel));
|
emit(CommentsState.loaded(comment: commentModel));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
@@ -101,13 +83,15 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
Future<bool> updateComment({required int id, required String body}) async {
|
Future<bool> updateComment({required int id, required String body}) async {
|
||||||
final commentModel = (state as CommentsStateLoaded).comment;
|
final commentModel = (state as CommentsStateLoaded).comment;
|
||||||
final updatedCommentModel =
|
final updatedCommentModel = commentModel.copyWith(
|
||||||
commentModel.copyWith(data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList());
|
data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList(),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await commentsRepository.updateComment(id: id, body: body);
|
await commentsRepository.updateComment(id: id, body: body);
|
||||||
emit(CommentsState.loaded(comment: updatedCommentModel));
|
emit(CommentsState.loaded(comment: updatedCommentModel));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
emit(CommentsState.loaded(comment: commentModel));
|
emit(CommentsState.loaded(comment: commentModel));
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,12 @@ class CommentsRepository {
|
|||||||
CommentsRepository({required this.service});
|
CommentsRepository({required this.service});
|
||||||
|
|
||||||
Future<void> deleteComment({required int id}) async {
|
Future<void> deleteComment({required int id}) async {
|
||||||
try {
|
return service.deleteComment(id);
|
||||||
return service.deleteComment(id);
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateComment({required int id, required String body}) async {
|
Future<void> updateComment({required int id, required String body}) async {
|
||||||
try {
|
return service.updateComment(id, CommentUpdateModel(body: body));
|
||||||
return service.updateComment(id, CommentUpdateModel(body: body));
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,4 +69,9 @@ class AppConstants {
|
|||||||
static const String usersEndpoint = '/users';
|
static const String usersEndpoint = '/users';
|
||||||
static const String postsEndpoint = '/posts';
|
static const String postsEndpoint = '/posts';
|
||||||
static const String commentsEndpoint = '/comments';
|
static const String commentsEndpoint = '/comments';
|
||||||
|
|
||||||
|
// Delay of pending requests
|
||||||
|
/// 30 секунд стоит потому что запрос может идти 12-15 секунд
|
||||||
|
/// Особенно если производится отладка на сервере разработчика
|
||||||
|
static const Duration pendingDelay = Duration(seconds: 30);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
|
|||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class MarketingCubit extends Cubit<MarketingCubitState> {
|
class MarketingCubit extends Cubit<MarketingCubitState> {
|
||||||
final Prefs prefs;
|
final Prefs prefs;
|
||||||
@@ -22,8 +23,9 @@ class MarketingCubit extends Cubit<MarketingCubitState> {
|
|||||||
await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true);
|
await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true);
|
||||||
}
|
}
|
||||||
return needToShowMarketingBottomSheet;
|
return needToShowMarketingBottomSheet;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
rethrow;
|
await reportFrom(e, st, this);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class StateUtils {
|
|||||||
if (isError(state)) {
|
if (isError(state)) {
|
||||||
// Попытка извлечь сообщение об ошибке из состояния
|
// Попытка извлечь сообщение об ошибке из состояния
|
||||||
final stateString = state.toString();
|
final stateString = state.toString();
|
||||||
final errorMatch = RegExp(r'error[:\s]+([^,}]+)').firstMatch(stateString);
|
final errorMatch = RegExp(r'error[:\s]+([^,}]+)', caseSensitive: false).firstMatch(stateString);
|
||||||
return errorMatch?.group(1)?.trim();
|
return errorMatch?.group(1)?.trim();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -39,15 +39,17 @@ import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cub
|
|||||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_cubit.dart';
|
||||||
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
|
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||||
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
|
||||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||||
@@ -75,11 +77,12 @@ class CubitRegistrations {
|
|||||||
/// Регистрация Cubit'ов как фабрик
|
/// Регистрация Cubit'ов как фабрик
|
||||||
static void _registerFactoryCubits() {
|
static void _registerFactoryCubits() {
|
||||||
// Auth related cubits
|
// Auth related cubits
|
||||||
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
|
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl(), profileRepository: sl()));
|
||||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl(), prefs: sl()));
|
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl(), prefs: sl()));
|
||||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||||
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
|
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl(), prefs: sl()));
|
||||||
sl.registerFactory<OAuthCubit>(() => OAuthCubit(authRepository: sl(), prefs: sl()));
|
sl.registerFactory<OAuthCubit>(() => OAuthCubit(authRepository: sl(), prefs: sl()));
|
||||||
|
sl.registerFactory<RegistrationCubitV2>(() => RegistrationCubitV2(registrationRepository: sl()));
|
||||||
// Feed related cubits
|
// Feed related cubits
|
||||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||||
@@ -112,7 +115,7 @@ class CubitRegistrations {
|
|||||||
// User content cubits
|
// User content cubits
|
||||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
|
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl(), recipeRepository: sl()));
|
||||||
|
|
||||||
// Other feature cubits
|
// Other feature cubits
|
||||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||||
@@ -133,6 +136,9 @@ class CubitRegistrations {
|
|||||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
||||||
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
|
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
|
||||||
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()));
|
||||||
|
sl.registerFactory<ConsultationPaymentCubit>(
|
||||||
|
() => ConsultationPaymentCubit(paymentRepository: sl(), sbpRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
||||||
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
|
import 'package:eatvio_app/core/constants/app_constants.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/sentry_error_reporter.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:sentry_dio/sentry_dio.dart';
|
||||||
|
|
||||||
/// Регистрация внешних зависимостей в системе внедрения зависимостей
|
/// Регистрация внешних зависимостей в системе внедрения зависимостей
|
||||||
class ExternalRegistrations {
|
class ExternalRegistrations {
|
||||||
@@ -12,6 +17,7 @@ class ExternalRegistrations {
|
|||||||
|
|
||||||
/// Регистрация всех внешних зависимостей
|
/// Регистрация всех внешних зависимостей
|
||||||
static void registerAll() {
|
static void registerAll() {
|
||||||
|
sl.registerLazySingleton<ErrorReporter>(SentryErrorReporter.new);
|
||||||
_registerDio();
|
_registerDio();
|
||||||
_registerPrefs();
|
_registerPrefs();
|
||||||
}
|
}
|
||||||
@@ -19,14 +25,23 @@ class ExternalRegistrations {
|
|||||||
/// Регистрация Dio HTTP клиента
|
/// Регистрация Dio HTTP клиента
|
||||||
static void _registerDio() {
|
static void _registerDio() {
|
||||||
sl.registerLazySingleton(
|
sl.registerLazySingleton(
|
||||||
() =>
|
() => Dio(
|
||||||
Dio(BaseOptions(baseUrl: sl<AppConfig>().baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
|
BaseOptions(
|
||||||
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
|
baseUrl: sl<AppConfig>().baseUrl,
|
||||||
|
headers: {'accept': 'application/json', 'content-type': 'application/json'},
|
||||||
|
connectTimeout: AppConstants.pendingDelay,
|
||||||
|
receiveTimeout: AppConstants.pendingDelay,
|
||||||
|
sendTimeout: AppConstants.pendingDelay,
|
||||||
|
),
|
||||||
|
)..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()])
|
||||||
|
..addSentry(
|
||||||
|
failedRequestStatusCodes: const [SentryStatusCode.defaultRange()],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Регистрация Prefs
|
/// Регистрация Prefs
|
||||||
static void _registerPrefs() {
|
static void _registerPrefs() {
|
||||||
sl.registerSingletonAsync<Prefs>(()async => Prefs(await SharedPreferences.getInstance()));
|
sl.registerSingletonAsync<Prefs>(() async => Prefs(await SharedPreferences.getInstance()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
|||||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/barcode/repositories/barcode_repository.dart';
|
||||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
||||||
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
|
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/payment/repositories/payment_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/payment/repositories/sbp_repository.dart';
|
||||||
import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart';
|
import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart';
|
||||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||||
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
|
||||||
@@ -30,7 +33,9 @@ import 'package:eatvio_app/features/profile/repositories/profile_repository.dart
|
|||||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||||
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
|
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
|
||||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
|
||||||
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/video_calls/repositories/video_call_repository.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
/// Регистрация всех Repository в системе внедрения зависимостей
|
/// Регистрация всех Repository в системе внедрения зависимостей
|
||||||
@@ -47,6 +52,7 @@ class RepositoryRegistrations {
|
|||||||
static void _registerSingletonRepositories() {
|
static void _registerSingletonRepositories() {
|
||||||
// Core repositories
|
// Core repositories
|
||||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||||
|
sl.registerLazySingleton<VideoCallRepository>(() => VideoCallRepository(videoCallService: sl()));
|
||||||
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
|
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
|
||||||
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
|
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
|
||||||
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
|
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
|
||||||
@@ -54,6 +60,7 @@ class RepositoryRegistrations {
|
|||||||
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
|
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
|
||||||
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
|
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
|
||||||
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
|
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
|
||||||
|
sl.registerLazySingleton<BarcodeRepository>(() => BarcodeRepository(barcodeService: sl(), dio: sl()));
|
||||||
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
|
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
|
||||||
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
|
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
|
||||||
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
||||||
@@ -69,7 +76,10 @@ class RepositoryRegistrations {
|
|||||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||||
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
||||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
||||||
|
sl.registerLazySingleton<SbpRepository>(() => SbpRepository(sbpService: sl()));
|
||||||
|
sl.registerLazySingleton<PaymentRepository>(() => PaymentRepository(chatService: sl()));
|
||||||
sl.registerLazySingleton<PersonalDietSuggestionRepository>(() => PersonalDietSuggestionRepository(personalDietSuggestionService: sl()));
|
sl.registerLazySingleton<PersonalDietSuggestionRepository>(() => PersonalDietSuggestionRepository(personalDietSuggestionService: sl()));
|
||||||
|
sl.registerLazySingleton<RegistrationRepositoryV2>(() => RegistrationRepositoryV2(registrationService: sl()));
|
||||||
|
|
||||||
// Content repositories
|
// Content repositories
|
||||||
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
|
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
|||||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||||
|
import 'package:eatvio_app/features/barcode/services/barcode_service.dart';
|
||||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||||
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
||||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||||
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||||
|
import 'package:eatvio_app/features/payment/services/sbp_service.dart';
|
||||||
import 'package:eatvio_app/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart';
|
import 'package:eatvio_app/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart';
|
||||||
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
||||||
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||||
@@ -32,7 +34,9 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
|||||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||||
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||||
|
import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart';
|
||||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||||
|
import 'package:eatvio_app/features/video_calls/services/video_call_service.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
/// Регистрация всех Service в системе внедрения зависимостей
|
/// Регистрация всех Service в системе внедрения зависимостей
|
||||||
@@ -48,6 +52,7 @@ class ServiceRegistrations {
|
|||||||
static void _registerSingletonServices() {
|
static void _registerSingletonServices() {
|
||||||
// Core services
|
// Core services
|
||||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||||
|
sl.registerLazySingleton<VideoCallService>(() => VideoCallService(sl()));
|
||||||
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
|
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
|
||||||
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
|
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
|
||||||
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
|
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
|
||||||
@@ -55,6 +60,7 @@ class ServiceRegistrations {
|
|||||||
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
|
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
|
||||||
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
|
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
|
||||||
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
|
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
|
||||||
|
sl.registerLazySingleton<BarcodeService>(() => BarcodeService(sl()));
|
||||||
sl.registerLazySingleton<DietService>(() => DietService(sl()));
|
sl.registerLazySingleton<DietService>(() => DietService(sl()));
|
||||||
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
|
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
|
||||||
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
||||||
@@ -69,7 +75,7 @@ class ServiceRegistrations {
|
|||||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi(errorReporter: sl()));
|
||||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||||
@@ -77,7 +83,9 @@ class ServiceRegistrations {
|
|||||||
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||||
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
||||||
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
|
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
|
||||||
|
sl.registerLazySingleton<SbpService>(() => const SbpServiceImpl());
|
||||||
sl.registerLazySingleton<PersonalDietSuggestionService>(() => PersonalDietSuggestionService(sl()));
|
sl.registerLazySingleton<PersonalDietSuggestionService>(() => PersonalDietSuggestionService(sl()));
|
||||||
|
sl.registerLazySingleton<RegistrationServiceV2>(() => RegistrationServiceV2(sl()));
|
||||||
|
|
||||||
// Content services
|
// Content services
|
||||||
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
|
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
|
||||||
|
|||||||
81
lib/core/error_reporting/error_report_policy.dart
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
|
/// Rules for which errors are sent to crash reporting.
|
||||||
|
///
|
||||||
|
/// [shouldReport] is used by [ErrorReporter]. [allowSentryEvent] is used in
|
||||||
|
/// [SentryFlutter.init] as a last-line filter (Dio failed-request capture, etc.).
|
||||||
|
class ErrorReportPolicy {
|
||||||
|
ErrorReportPolicy._();
|
||||||
|
|
||||||
|
/// When `false`, the error is not sent via [ErrorReporter].
|
||||||
|
static bool shouldReport(Object? error) {
|
||||||
|
if (error == null) return false;
|
||||||
|
if (error is String) return false;
|
||||||
|
if (error is SocketException) return false;
|
||||||
|
|
||||||
|
if (error is DioException) {
|
||||||
|
return _shouldReportDio(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in SDK [SentryOptions.beforeSend]. Return `false` to drop the event.
|
||||||
|
///
|
||||||
|
/// For [DioException], rules differ from [shouldReport]: events from `sentry_dio`
|
||||||
|
/// (e.g. 5xx) must pass through here, while [shouldReport] returns false for 5xx
|
||||||
|
/// so [SentryErrorReporter] does not duplicate them after `catch` in cubits / manual [report] calls.
|
||||||
|
static bool allowSentryEvent(SentryEvent event, Hint hint) {
|
||||||
|
final t = event.throwable;
|
||||||
|
if (t == null) return true;
|
||||||
|
if (t is DioException) {
|
||||||
|
return _allowSentrySdkDio(t);
|
||||||
|
}
|
||||||
|
return shouldReport(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel, timeouts, socket / connection errors — do not send (noisy, often user environment).
|
||||||
|
static bool _isTransientDioFailure(DioException e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case DioExceptionType.cancel:
|
||||||
|
case DioExceptionType.connectionTimeout:
|
||||||
|
case DioExceptionType.sendTimeout:
|
||||||
|
case DioExceptionType.receiveTimeout:
|
||||||
|
case DioExceptionType.connectionError:
|
||||||
|
case DioExceptionType.transformTimeout:
|
||||||
|
return true;
|
||||||
|
case DioExceptionType.unknown:
|
||||||
|
case DioExceptionType.badResponse:
|
||||||
|
case DioExceptionType.badCertificate:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return e.error is SocketException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Noise to drop for any Dio-originated SDK event; do not use for manual [report].
|
||||||
|
static bool _allowSentrySdkDio(DioException e) {
|
||||||
|
if (_isTransientDioFailure(e)) return false;
|
||||||
|
final code = e.response?.statusCode;
|
||||||
|
if (code != null && code >= 400 && code < 500) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _shouldReportDio(DioException e) {
|
||||||
|
if (_isTransientDioFailure(e)) return false;
|
||||||
|
|
||||||
|
final code = e.response?.statusCode;
|
||||||
|
if (code != null) {
|
||||||
|
if (code >= 500 && code <= 599) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (code >= 400 && code < 500) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
lib/core/error_reporting/error_reporter.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/// Application-wide error reporting (e.g. Sentry).
|
||||||
|
abstract class ErrorReporter {
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
});
|
||||||
|
}
|
||||||
14
lib/core/error_reporting/no_op_error_reporter.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
|
||||||
|
/// Reporting sink that does nothing (dev tooling, tests, diagnostics).
|
||||||
|
class NoOpErrorReporter implements ErrorReporter {
|
||||||
|
const NoOpErrorReporter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
}) async {}
|
||||||
|
}
|
||||||
17
lib/core/error_reporting/report_from.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/no_op_error_reporter.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
|
||||||
|
/// Отправка в [ErrorReporter] с тегом origin = [context.runtimeType] (имя кубита/виджета и т.д.).
|
||||||
|
///
|
||||||
|
/// Если [ErrorReporter] не зарегистрирован (например, после [GetIt.reset] в тестах), используется [NoOpErrorReporter].
|
||||||
|
Future<void> reportFrom(Object error, StackTrace? stackTrace, Object context) {
|
||||||
|
final reporter = sl.isRegistered<ErrorReporter>()
|
||||||
|
? sl<ErrorReporter>()
|
||||||
|
: const NoOpErrorReporter();
|
||||||
|
return reporter.report(
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
origin: context.runtimeType.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
33
lib/core/error_reporting/sentry_error_reporter.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
|
class SentryErrorReporter implements ErrorReporter {
|
||||||
|
SentryErrorReporter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> report(
|
||||||
|
Object error,
|
||||||
|
StackTrace? stackTrace, {
|
||||||
|
String? origin,
|
||||||
|
Map<String, Object?>? extras,
|
||||||
|
}) async {
|
||||||
|
if (!ErrorReportPolicy.shouldReport(error)) return;
|
||||||
|
|
||||||
|
await Sentry.captureException(
|
||||||
|
error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
withScope: (scope) {
|
||||||
|
if (origin != null && origin.isNotEmpty) {
|
||||||
|
scope.setTag('origin', origin);
|
||||||
|
}
|
||||||
|
if (extras != null && extras.isNotEmpty) {
|
||||||
|
scope.setContexts(
|
||||||
|
'report_extras',
|
||||||
|
extras.map((k, v) => MapEntry(k, v?.toString())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,47 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/app_initializer.dart';
|
import 'package:eatvio_app/core/app_initializer.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/services.dart' show MethodChannel, PlatformException;
|
import 'package:flutter/services.dart' show MethodChannel, PlatformException;
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
|
|
||||||
class FirebaseApi {
|
class FirebaseApi {
|
||||||
|
final ErrorReporter _errorReporter;
|
||||||
final _firebaseMessaging = FirebaseMessaging.instance;
|
final _firebaseMessaging = FirebaseMessaging.instance;
|
||||||
|
|
||||||
|
FirebaseApi({required ErrorReporter errorReporter}) : _errorReporter = errorReporter;
|
||||||
|
|
||||||
Future<String?> getToken() async {
|
Future<String?> getToken() async {
|
||||||
String? token;
|
String? token;
|
||||||
|
|
||||||
if (await _isHuaweiDevice()) {
|
if (await _isHuaweiDevice()) {
|
||||||
token = await _getHuaweiPushTokenNative();
|
token = await _getHuaweiPushTokenNative().timeout(
|
||||||
|
const Duration(seconds: 5),
|
||||||
|
onTimeout: () => null,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if ((sl<AppConfig>().buildType == AppBuildType.prod)) {
|
if ((sl<AppConfig>().buildType == AppBuildType.prod)) {
|
||||||
await _firebaseMessaging.requestPermission();
|
try {
|
||||||
|
await _firebaseMessaging.requestPermission().timeout(
|
||||||
|
const Duration(seconds: 5),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Разрешение не критично, продолжаем
|
||||||
|
log('Failed to request Firebase messaging permission: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
token = await _firebaseMessaging.getToken();
|
token = await _firebaseMessaging.getToken().timeout(
|
||||||
|
const Duration(seconds: 5),
|
||||||
|
onTimeout: () => null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -35,13 +52,20 @@ class FirebaseApi {
|
|||||||
await sl<Dio>().post(
|
await sl<Dio>().post(
|
||||||
'/api/anonymous_token',
|
'/api/anonymous_token',
|
||||||
data: {
|
data: {
|
||||||
'platform': Platform.isIOS ? 'ios' : 'android',
|
'platform': Platform.isIOS ? 'ios' : (await _isHuaweiDevice()) ? 'huawei' : 'android',
|
||||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||||
'token': await getToken(),
|
'token': await getToken(),
|
||||||
},
|
},
|
||||||
|
).timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
onTimeout: () {
|
||||||
|
throw TimeoutException('registerAnonymousPushToken timeout');
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
|
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +81,16 @@ class FirebaseApi {
|
|||||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||||
'token': await getToken(),
|
'token': await getToken(),
|
||||||
},
|
},
|
||||||
|
).timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
onTimeout: () {
|
||||||
|
throw TimeoutException('registerPushToken timeout');
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
|
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,10 +115,12 @@ class FirebaseApi {
|
|||||||
} on PlatformException catch (e, st) {
|
} on PlatformException catch (e, st) {
|
||||||
showErrorToast('$e $st');
|
showErrorToast('$e $st');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
return null;
|
return null;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
showErrorToast('$e $st');
|
showErrorToast('$e $st');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
|
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:eatvio_app/core/app_initializer.dart';
|
||||||
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
abstract class ImageCompressUtility {
|
abstract class ImageCompressUtility {
|
||||||
@@ -27,33 +29,21 @@ abstract class ImageCompressUtility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<File> compressFile(File file) async {
|
static Future<File> compressFile(File file) async {
|
||||||
final image = img.decodeImage(file.readAsBytesSync())!;
|
try {
|
||||||
int width = image.width;
|
final appConfig = sl<AppConfig>();
|
||||||
int height = image.height;
|
if (appConfig.buildType == AppBuildType.tests) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
if (width > height) {
|
final compressedPath = '${p.withoutExtension(file.path)}_compressed.jpg';
|
||||||
if (width > 1080) {
|
|
||||||
height = (height * 1080) ~/ width;
|
final result = await FlutterImageCompress.compressAndGetFile(file.absolute.path, compressedPath, quality: 90, minHeight: 1080, minWidth: 1080);
|
||||||
width = 1080;
|
|
||||||
}
|
if (result == null) {
|
||||||
} else {
|
return file;
|
||||||
if (height > 1080) {
|
|
||||||
width = (width * 1080) ~/ height;
|
|
||||||
height = 1080;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String filePathWithoutExtension = p.withoutExtension(file.path);
|
return File(result.path);
|
||||||
String compressedFilePath = '${filePathWithoutExtension}_compressed.jpg';
|
|
||||||
|
|
||||||
final result = await FlutterImageCompress.compressAndGetFile(
|
|
||||||
file.absolute.path,
|
|
||||||
compressedFilePath,
|
|
||||||
quality: 90,
|
|
||||||
minHeight: height,
|
|
||||||
minWidth: width,
|
|
||||||
);
|
|
||||||
|
|
||||||
return File(result!.path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -12,18 +13,21 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
|
|||||||
final data = err.response?.data;
|
final data = err.response?.data;
|
||||||
|
|
||||||
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
|
||||||
final String phone = await sl<ProfileRepository>().getMyPhone();
|
final String phone = await sl<ProfileRepository>().getMyPhone();
|
||||||
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
|
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
|
||||||
await showErrorDialog(AppRouter.navigatorKey.currentContext!,
|
await showErrorDialog(
|
||||||
title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона');
|
AppRouter.navigatorKey.currentContext!,
|
||||||
|
title: 'Привяжите номер телефона',
|
||||||
|
description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона',
|
||||||
|
);
|
||||||
|
final currentContext = AppRouter.navigatorKey.currentContext;
|
||||||
|
|
||||||
|
if (currentContext != null && currentContext.mounted && GoRouter.of(currentContext).state.path == RouteConstants.stepperBecomeConsultantScreen) {
|
||||||
|
GoRouter.of(currentContext).pop();
|
||||||
|
}
|
||||||
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
|
||||||
return handler.reject(
|
return handler.reject(
|
||||||
DioException(
|
DioException(requestOptions: err.requestOptions, response: err.response, error: 'Phone number not verified', type: DioExceptionType.badResponse),
|
||||||
requestOptions: err.requestOptions,
|
|
||||||
response: err.response,
|
|
||||||
error: 'Phone number not verified',
|
|
||||||
type: DioExceptionType.badResponse,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class TokenInterceptor extends Interceptor {
|
class TokenInterceptor extends Interceptor {
|
||||||
@@ -23,6 +25,7 @@ class TokenInterceptor extends Interceptor {
|
|||||||
ErrorInterceptorHandler handler,
|
ErrorInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
if (err.response?.statusCode == 401) {
|
if (err.response?.statusCode == 401) {
|
||||||
|
await sl<WsManager>().clear();
|
||||||
await TokenStorage.removeTokenInStorage();
|
await TokenStorage.removeTokenInStorage();
|
||||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:eatvio_app/core/utils/toasts.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
||||||
@@ -33,7 +34,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
result = await _connectivity.checkConnectivity();
|
result = await _connectivity.checkConnectivity();
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
log('Couldn\'t check connectivity status', error: e);
|
log('Couldn\'t check connectivity status', error: e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -42,9 +44,13 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
|
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
|
||||||
final hasInternet = await _checkInternetAccess(result);
|
try {
|
||||||
if (!hasInternet) showInternetConnectionToast();
|
final hasInternet = await _checkInternetAccess(result);
|
||||||
emit(state.copyWith(isInternetAvailable: hasInternet));
|
if (!hasInternet) showInternetConnectionToast();
|
||||||
|
emit(state.copyWith(isInternetAvailable: hasInternet));
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
|
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
|
||||||
@@ -57,7 +63,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
|||||||
try {
|
try {
|
||||||
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
||||||
return response.statusCode >= 100 && response.statusCode < 600;
|
return response.statusCode >= 100 && response.statusCode < 600;
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
|||||||
import 'package:eatvio_app/core/di/external_registrations.dart';
|
import 'package:eatvio_app/core/di/external_registrations.dart';
|
||||||
import 'package:eatvio_app/core/di/repository_registrations.dart';
|
import 'package:eatvio_app/core/di/repository_registrations.dart';
|
||||||
import 'package:eatvio_app/core/di/service_registrations.dart';
|
import 'package:eatvio_app/core/di/service_registrations.dart';
|
||||||
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ Future<void> init({required AppBuildType buildType}) async {
|
|||||||
// Регистрация внешних зависимостей (Dio, etc.)
|
// Регистрация внешних зависимостей (Dio, etc.)
|
||||||
ExternalRegistrations.registerAll();
|
ExternalRegistrations.registerAll();
|
||||||
|
|
||||||
|
// Ожидание инициализации асинхронных зависимостей
|
||||||
|
await sl.getAsync<Prefs>();
|
||||||
|
|
||||||
// Регистрация сервисов
|
// Регистрация сервисов
|
||||||
ServiceRegistrations.registerAll();
|
ServiceRegistrations.registerAll();
|
||||||
|
|
||||||
@@ -28,5 +32,5 @@ Future<void> init({required AppBuildType buildType}) async {
|
|||||||
CubitRegistrations.registerAll();
|
CubitRegistrations.registerAll();
|
||||||
|
|
||||||
// Регистрация WebSocket менеджера
|
// Регистрация WebSocket менеджера
|
||||||
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
|
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl(), errorReporter: sl()));
|
||||||
}
|
}
|
||||||
|
|||||||
64
lib/core/notifications/push_navigation_handler.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class PushNavigationHandler {
|
||||||
|
static bool _initialized = false;
|
||||||
|
static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push');
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
|
||||||
|
if (initialMessage != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_handleMessageInternal(initialMessage, source: 'initialMessage');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
developer.log('PushNavigationHandler: error while getting initialMessage: $e', name: 'PushNavigationHandler');
|
||||||
|
}
|
||||||
|
|
||||||
|
FirebaseMessaging.onMessageOpenedApp.listen((message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'));
|
||||||
|
|
||||||
|
_hmsChannel.setMethodCallHandler((call) async {
|
||||||
|
if (call.method == 'openActionUrl') {
|
||||||
|
final url = call.arguments as String? ?? '';
|
||||||
|
handleActionUrlFromNative(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleActionUrlFromNative(String url) {
|
||||||
|
if (url.isEmpty) {
|
||||||
|
developer.log('PushNavigationHandler.handleActionUrlFromNative: empty url', name: 'PushNavigationHandler');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url', name: 'PushNavigationHandler');
|
||||||
|
|
||||||
|
AppRouter.navigateByDeepLinkUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleMessage(RemoteMessage message) {
|
||||||
|
_handleMessageInternal(message, source: 'manualHandle');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _handleMessageInternal(RemoteMessage message, {required String source}) {
|
||||||
|
final data = message.data;
|
||||||
|
final actionUrl = data['action_url'] as String?;
|
||||||
|
if (actionUrl == null || actionUrl.isEmpty) {
|
||||||
|
developer.log('PushNavigationHandler: no action_url in message data, source=$source, data=$data', name: 'PushNavigationHandler');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
developer.log('PushNavigationHandler: navigating by action_url=$actionUrl, source=$source', name: 'PushNavigationHandler');
|
||||||
|
|
||||||
|
AppRouter.navigateByDeepLinkUrl(actionUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
enum PrefsKeys { hasAnonymousTokenBeenSent, welcomeScreenWasShowed, registrationDate, marketingBottomSheetHasBeenShown }
|
enum PrefsKeys { hasAnonymousTokenBeenSent, welcomeScreenWasShowed, registrationDate, marketingBottomSheetHasBeenShown, isEmailVerificationSkipped }
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:eatvio_app/core/firebase_api.dart';
|
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
|
||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||||
@@ -9,23 +7,20 @@ import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.
|
|||||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Маршруты для аутентификации
|
|
||||||
class AuthRoutes {
|
class AuthRoutes {
|
||||||
/// Создание маршрутов аутентификации
|
|
||||||
static List<RouteBase> createRoutes() {
|
static List<RouteBase> createRoutes() {
|
||||||
return [
|
return [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.authScreen,
|
path: RouteConstants.authScreen,
|
||||||
name: 'authScreen',
|
name: 'authScreen',
|
||||||
builder: (context, state) => const AuthScreen(),
|
builder: (context, state) => const AuthScreen(),
|
||||||
redirect: (context, state) {
|
|
||||||
sl<FirebaseApi>().registerAnonymousPushToken();
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||||
|
GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.forgetPasswordScreen,
|
path: RouteConstants.forgetPasswordScreen,
|
||||||
name: 'forgetPasswordScreen',
|
name: 'forgetPasswordScreen',
|
||||||
|
|||||||
@@ -17,12 +17,20 @@ class ContentRoutes {
|
|||||||
name: 'aboutPlanScreen',
|
name: 'aboutPlanScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
|
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
|
return AboutPlanScreen(slug: slug, navigateToComments: commentId != null, index: null, highlightedCommentId: commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
return AboutPlanScreen(
|
||||||
|
slug: data['slug'] as String,
|
||||||
|
index: data['index'] as int?,
|
||||||
|
navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
|
||||||
|
highlightedCommentId: data['commentId'] as int?,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -30,12 +38,20 @@ class ContentRoutes {
|
|||||||
name: 'aboutRecipesScreen',
|
name: 'aboutRecipesScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
|
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
|
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: commentId != null, index: null, highlightedCommentId: commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
return AboutRecipeScreen(
|
||||||
|
recipeSlug: data['recipe'] as String,
|
||||||
|
index: data['index'] as int?,
|
||||||
|
navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
|
||||||
|
highlightedCommentId: data['commentId'] as int?,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -43,16 +59,28 @@ class ContentRoutes {
|
|||||||
name: 'aboutDietScreen',
|
name: 'aboutDietScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
final idStr = state.uri.queryParameters['id'];
|
||||||
|
final id = int.tryParse(idStr ?? '');
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
|
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: false);
|
return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: commentId != null, highlightedCommentId: commentId);
|
||||||
}
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
if (id != null && id > 0) {
|
||||||
return AboutDietScreen(
|
return AboutDietScreen(id: id, slug: null, index: null, navigateToComments: commentId != null, highlightedCommentId: commentId);
|
||||||
id: data['id'] as int?,
|
}
|
||||||
slug: data['slug'] as String?,
|
final data = state.extra as Map<String, dynamic>?;
|
||||||
index: data['index'] as int?,
|
if (data != null) {
|
||||||
navigateToComments: data['navigateToComments'] as bool,
|
return AboutDietScreen(
|
||||||
);
|
id: data['id'] as int?,
|
||||||
|
slug: data['slug'] as String?,
|
||||||
|
index: data['index'] as int?,
|
||||||
|
navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null),
|
||||||
|
highlightedCommentId: data['commentId'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const AboutDietScreen(id: null, slug: null, index: null, navigateToComments: false, highlightedCommentId: null);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -65,12 +93,20 @@ class ContentRoutes {
|
|||||||
name: 'blogPostScreen',
|
name: 'blogPostScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final id = state.uri.queryParameters['id'];
|
final id = state.uri.queryParameters['id'];
|
||||||
|
final commentIdStr = state.uri.queryParameters['commentId'];
|
||||||
|
final commentId = int.tryParse(commentIdStr ?? '');
|
||||||
final parsedId = int.tryParse(id ?? '');
|
final parsedId = int.tryParse(id ?? '');
|
||||||
|
|
||||||
if (parsedId != null) {
|
if (parsedId != null) {
|
||||||
return BlogPostScreen(id: parsedId);
|
return BlogPostScreen(id: parsedId, highlightedCommentId: commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BlogPostScreen(id: state.extra as int);
|
if (state.extra is int) {
|
||||||
|
return BlogPostScreen(id: state.extra as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = state.extra as Map<String, dynamic>;
|
||||||
|
return BlogPostScreen(id: data['id'] as int, highlightedCommentId: data['commentId'] as int?);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||||
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||||
@@ -23,14 +24,16 @@ class MainNavigationRoutes {
|
|||||||
return [
|
return [
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
return BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
// Инициализация ProfileAvatarCubit при первом использовании ShellRoute
|
||||||
builder: (BuildContext context, ProfileAvatarState state) {
|
final profileAvatarCubit = context.read<ProfileAvatarCubit>();
|
||||||
if (state is ProfileAvatarStateLoaded) {
|
if (profileAvatarCubit.state is ProfileAvatarStateLoading) {
|
||||||
return BottomNavigationBarWidget(profileModel: state.profile, child: child);
|
// Вызываем getMyProfile() только если состояние loading (первый запуск)
|
||||||
}
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
return const SizedBox.shrink();
|
profileAvatarCubit.getMyProfile();
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return _MainShellWithNavScope(currentLocation: state.matchedLocation, child: child);
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -43,7 +46,14 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.feedScreen,
|
path: RouteConstants.feedScreen,
|
||||||
name: 'feedScreen',
|
name: 'feedScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
pageBuilder: (context, state) {
|
||||||
|
final initialTabIndexFromQuery = int.tryParse(state.uri.queryParameters['initialTabIndex'] ?? '');
|
||||||
|
final extra = state.extra;
|
||||||
|
if (extra is int) {
|
||||||
|
return NoTransitionPage(child: FeedScreen(initialTabIndex: extra));
|
||||||
|
}
|
||||||
|
return NoTransitionPage(child: FeedScreen(initialTabIndex: initialTabIndexFromQuery));
|
||||||
|
},
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
try {
|
try {
|
||||||
await sl<WsManager>().initialize();
|
await sl<WsManager>().initialize();
|
||||||
@@ -56,7 +66,7 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.diaryScreen,
|
path: RouteConstants.diaryScreen,
|
||||||
name: 'diaryScreen',
|
name: 'diaryScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
pageBuilder: (context, state) => NoTransitionPage(child: DiaryScreen(date: state.extra as DateTime?)),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: ConsultationsScreen.path,
|
path: ConsultationsScreen.path,
|
||||||
@@ -66,7 +76,10 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.chatSelectScreen,
|
path: RouteConstants.chatSelectScreen,
|
||||||
name: 'chatSelectScreen',
|
name: 'chatSelectScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
|
pageBuilder: (context, state) {
|
||||||
|
final initialSearchQuery = state.extra is String ? state.extra as String : null;
|
||||||
|
return NoTransitionPage(child: ChatsScreen(initialSearchQuery: initialSearchQuery));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.notificationsScreen,
|
path: RouteConstants.notificationsScreen,
|
||||||
@@ -76,19 +89,34 @@ class MainNavigationRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.menuScreen,
|
path: RouteConstants.menuScreen,
|
||||||
name: 'menuScreen',
|
name: 'menuScreen',
|
||||||
pageBuilder: (context, state) => NoTransitionPage(
|
pageBuilder: (context, state) => const NoTransitionPage(child: MenuScreen()),
|
||||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
|
||||||
builder: (BuildContext context, ProfileAvatarState state) {
|
|
||||||
if (state is ProfileAvatarStateLoaded) {
|
|
||||||
return MenuScreen(profileModel: state.profile);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications)
|
||||||
|
/// can switch tabs via [BottomNavController.switchToRoute] instead of pushing.
|
||||||
|
class _MainShellWithNavScope extends StatefulWidget {
|
||||||
|
const _MainShellWithNavScope({required this.child, required this.currentLocation});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final String currentLocation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_MainShellWithNavScope> createState() => _MainShellWithNavScopeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainShellWithNavScopeState extends State<_MainShellWithNavScope> {
|
||||||
|
final BottomNavController _navController = BottomNavController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BottomNavScope(
|
||||||
|
controller: _navController,
|
||||||
|
child: BottomNavigationBarWidget(currentLocation: widget.currentLocation, child: widget.child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/firebase_api.dart';
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
@@ -6,12 +7,14 @@ import 'package:eatvio_app/core/prefs.dart';
|
|||||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/video_calls/presentation/pages/video_call_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||||
@@ -58,24 +61,33 @@ class OtherRoutes {
|
|||||||
builder: (context, state) => const WelcomeScreen(),
|
builder: (context, state) => const WelcomeScreen(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
try {
|
try {
|
||||||
final prefs = sl<Prefs>();
|
// Prefs уже инициализирован в locator_service, но на всякий случай проверяем
|
||||||
final firebaseApi = sl<FirebaseApi>();
|
final prefs = await sl.getAsync<Prefs>();
|
||||||
final welcomeScreenWasShowed = prefs.getBool(PrefsKeys.welcomeScreenWasShowed) ?? false;
|
final welcomeScreenWasShowed = prefs.getBool(PrefsKeys.welcomeScreenWasShowed) ?? false;
|
||||||
await firebaseApi.registerPushToken();
|
|
||||||
if (!welcomeScreenWasShowed) {
|
if (!welcomeScreenWasShowed) {
|
||||||
return null;
|
return null;
|
||||||
} else if (await TokenStorage.haveTokenInStorage()) {
|
}
|
||||||
return '/feed-screen';
|
|
||||||
} else {
|
if (await TokenStorage.haveTokenInStorage()) {
|
||||||
final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
|
try {
|
||||||
if (!hasAnonymousTokenBeenSent) {
|
final profileRepository = sl<ProfileRepository>();
|
||||||
await firebaseApi.registerAnonymousPushToken();
|
final profile = await profileRepository.getMyProfile();
|
||||||
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
|
|
||||||
|
final firebaseApi = sl<FirebaseApi>();
|
||||||
|
await firebaseApi.registerPushToken();
|
||||||
|
|
||||||
|
if (profile.emailVerifiedAt != null || (prefs.getBool(PrefsKeys.isEmailVerificationSkipped) ?? false)) {
|
||||||
|
return RouteConstants.feedScreen;
|
||||||
|
}
|
||||||
|
return RouteConstants.verifyEmailScreen;
|
||||||
|
} catch (e) {
|
||||||
|
return '/auth-screen';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return '/auth-screen';
|
return '/auth-screen';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '/feed-screen';
|
return '/auth-screen';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -85,7 +97,27 @@ class OtherRoutes {
|
|||||||
builder: (context, state) => const UpdateScreen(),
|
builder: (context, state) => const UpdateScreen(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
try {
|
try {
|
||||||
final versions = (await sl<Dio>().get('${sl<AppConfig>().baseUrl}/api/app_versions')).data;
|
// Проверка версии с таймаутом, чтобы не блокировать запуск приложения
|
||||||
|
final versions = await sl<Dio>()
|
||||||
|
.get('${sl<AppConfig>().baseUrl}/api/app_versions')
|
||||||
|
.timeout(
|
||||||
|
const Duration(seconds: 5),
|
||||||
|
onTimeout: () {
|
||||||
|
// При таймауте пропускаем проверку и идем дальше
|
||||||
|
throw TimeoutException('Version check timeout');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((response) => response.data)
|
||||||
|
.catchError((e) {
|
||||||
|
// При любой ошибке пропускаем проверку версии
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (versions == null) {
|
||||||
|
// Если проверка не удалась, пропускаем экран обновления
|
||||||
|
return RouteConstants.welcomeScreen;
|
||||||
|
}
|
||||||
|
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
String version = packageInfo.version;
|
String version = packageInfo.version;
|
||||||
Version currentVersion = Version.parse(version);
|
Version currentVersion = Version.parse(version);
|
||||||
@@ -96,6 +128,7 @@ class OtherRoutes {
|
|||||||
return RouteConstants.welcomeScreen;
|
return RouteConstants.welcomeScreen;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// При любой ошибке пропускаем экран обновления
|
||||||
return RouteConstants.welcomeScreen;
|
return RouteConstants.welcomeScreen;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -121,22 +154,32 @@ class OtherRoutes {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RecipeCreateScreen.path,
|
path: RecipeCreateScreen.path,
|
||||||
name: RecipeCreateScreen.name,
|
name: RecipeCreateScreen.name,
|
||||||
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
builder: (context, state) {
|
||||||
|
if (state.extra is Map<String, dynamic>) {
|
||||||
|
final data = state.extra as Map<String, dynamic>;
|
||||||
|
return RecipeCreateScreen(recipe: data['recipe'] as Recipe?, autoValidate: data['autoValidate'] as bool? ?? false);
|
||||||
|
}
|
||||||
|
return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: CreateDietScreen.path,
|
path: CreateDietScreen.path,
|
||||||
name: CreateDietScreen.name,
|
name: CreateDietScreen.name,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final diet = state.extra as Diet?;
|
final data = state.extra as Map<String, dynamic>?;
|
||||||
return CreateDietScreen(diet: diet);
|
final diet = data?['diet'] as Diet?;
|
||||||
|
final isEditing = data?['isEditing'] as bool?;
|
||||||
|
return CreateDietScreen(diet: diet, isEditing: isEditing);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: DietPreviewScreen.path,
|
path: DietPreviewScreen.path,
|
||||||
name: DietPreviewScreen.name,
|
name: DietPreviewScreen.name,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final data = state.extra as Diet;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return DietPreviewScreen(diet: data);
|
final diet = data['diet'] as Diet;
|
||||||
|
final isImageInBase64 = data['isImageInBase64'] as bool;
|
||||||
|
return DietPreviewScreen(diet: diet, isImageInBase64: isImageInBase64);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()),
|
GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()),
|
||||||
@@ -157,9 +200,9 @@ class OtherRoutes {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.subsScreen,
|
path: '${RouteConstants.subsScreen}/:slug',
|
||||||
name: 'subsScreen',
|
name: 'subsScreen',
|
||||||
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
|
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug'], subType: state.extra as SubType),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Chat routes
|
// Chat routes
|
||||||
@@ -167,10 +210,24 @@ class OtherRoutes {
|
|||||||
path: RouteConstants.chatScreen,
|
path: RouteConstants.chatScreen,
|
||||||
name: 'chatScreen',
|
name: 'chatScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final uuidFromQuery = state.uri.queryParameters['uuid'];
|
||||||
|
final orderIdFromQuery = state.uri.queryParameters['orderId'];
|
||||||
|
if (uuidFromQuery != null || orderIdFromQuery != null) {
|
||||||
|
return ChatScreen(
|
||||||
|
uuid: uuidFromQuery,
|
||||||
|
orderId: orderIdFromQuery,
|
||||||
|
sendInitialMessage: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final uuid = data['uuid'] as String;
|
final uuid = data['uuid'] as String?;
|
||||||
|
final orderId = data['orderId'] as String?;
|
||||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||||
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
|
return ChatScreen(
|
||||||
|
uuid: uuid,
|
||||||
|
orderId: orderId,
|
||||||
|
sendInitialMessage: sendInitialMessage,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -178,13 +235,39 @@ class OtherRoutes {
|
|||||||
name: 'emptyChatScreen',
|
name: 'emptyChatScreen',
|
||||||
builder: (context, state) => EmptyChatScreen(user: state.extra as ProfileModel),
|
builder: (context, state) => EmptyChatScreen(user: state.extra as ProfileModel),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: RouteConstants.videoCallScreen,
|
||||||
|
name: VideoCallScreen.name,
|
||||||
|
builder: (context, state) {
|
||||||
|
final extra = state.extra;
|
||||||
|
if (extra is Map<String, dynamic>) {
|
||||||
|
return VideoCallScreen(
|
||||||
|
conversationUuid: extra['conversationUuid'] as String,
|
||||||
|
counterpartName: extra['counterpartName'] as String?,
|
||||||
|
localParticipantName: extra['localParticipantName'] as String?,
|
||||||
|
onBackToChat: extra['onBackToChat'] as void Function()?,
|
||||||
|
onHangUp: extra['onHangUp'] as void Function()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final uuid = state.uri.queryParameters['uuid'];
|
||||||
|
if (uuid != null) {
|
||||||
|
return VideoCallScreen(conversationUuid: uuid);
|
||||||
|
}
|
||||||
|
throw ArgumentError('VideoCallScreen: pass extra Map with conversationUuid or query uuid');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
// Consultant routes
|
// Consultant routes
|
||||||
GoRoute(path: RouteConstants.becomeConsultantScreen, name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
GoRoute(path: RouteConstants.becomeConsultantScreen, name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.stepperBecomeConsultantScreen,
|
path: RouteConstants.stepperBecomeConsultantScreen,
|
||||||
name: 'stepperBecomeConsultantScreen',
|
name: 'stepperBecomeConsultantScreen',
|
||||||
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
|
builder: (context, state) {
|
||||||
|
final data = state.extra as Map<String, dynamic>;
|
||||||
|
final slug = data['slug'] as String?;
|
||||||
|
final isCanceled = data['isCanceled'] as bool?;
|
||||||
|
return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||||
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||||
@@ -195,7 +278,11 @@ class OtherRoutes {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Other routes
|
// Other routes
|
||||||
GoRoute(path: RouteConstants.glossaryScreen, name: 'glossaryScreen', builder: (context, state) => GlossaryScreen(passKey: state.extra as String)),
|
GoRoute(
|
||||||
|
path: RouteConstants.glossaryScreen,
|
||||||
|
name: 'glossaryScreen',
|
||||||
|
builder: (context, state) => GlossaryScreen(passKey: state.extra as String),
|
||||||
|
),
|
||||||
GoRoute(path: RouteConstants.blockedUsersScreen, name: 'blockedUsersScreen', builder: (context, state) => const BlockedUsersScreen()),
|
GoRoute(path: RouteConstants.blockedUsersScreen, name: 'blockedUsersScreen', builder: (context, state) => const BlockedUsersScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteConstants.notificationsSettingsScreen,
|
path: RouteConstants.notificationsSettingsScreen,
|
||||||
@@ -213,6 +300,11 @@ class OtherRoutes {
|
|||||||
path: InfoByOrderScreen.path,
|
path: InfoByOrderScreen.path,
|
||||||
name: InfoByOrderScreen.name,
|
name: InfoByOrderScreen.name,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final queryParams = state.uri.queryParameters;
|
||||||
|
final planIdFromQuery = int.tryParse(queryParams['planId'] ?? '');
|
||||||
|
if (planIdFromQuery != null) {
|
||||||
|
return InfoByOrderScreen(planId: planIdFromQuery, successfulPayment: null, customerSlug: null, isConsultantView: true);
|
||||||
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final planId = data['planId'] as int;
|
final planId = data['planId'] as int;
|
||||||
final successfulPayment = data['successfulPayment'] as bool?;
|
final successfulPayment = data['successfulPayment'] as bool?;
|
||||||
@@ -236,14 +328,13 @@ class OtherRoutes {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final data = state.extra as Map<String, int>;
|
final data = state.extra as Map<String, int>;
|
||||||
final diaryId = data['diaryId'] as int;
|
final diaryId = data['diaryId'] as int;
|
||||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
return UserDiaryScreen(diaryId: diaryId);
|
||||||
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: PersonalDietSuggestionScreen.path,
|
path: PersonalDietSuggestionScreen.path,
|
||||||
name: PersonalDietSuggestionScreen.name,
|
name: PersonalDietSuggestionScreen.name,
|
||||||
builder: (context, state) => PersonalDietSuggestionScreen(dietId: state.extra as int,),
|
builder: (context, state) => PersonalDietSuggestionScreen(dietId: state.extra as int),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ class RouteConstants {
|
|||||||
// Auth routes
|
// Auth routes
|
||||||
static const String authScreen = '/auth-screen';
|
static const String authScreen = '/auth-screen';
|
||||||
static const String registrationScreen = 'registration-screen';
|
static const String registrationScreen = 'registration-screen';
|
||||||
|
static const String registrationScreenV2 = 'registration-screen-v2';
|
||||||
static const String forgetPasswordScreen = 'forget-password-screen';
|
static const String forgetPasswordScreen = 'forget-password-screen';
|
||||||
static const String scanQRCodeScreen = 'scan-qr-code-screen';
|
static const String scanQRCodeScreen = 'scan-qr-code-screen';
|
||||||
static const String verifyScreen = '/verify-screen';
|
static const String verifyScreen = '/verify-screen';
|
||||||
@@ -52,6 +53,7 @@ class RouteConstants {
|
|||||||
static const String photoViewScreen = '/photo-view-screen';
|
static const String photoViewScreen = '/photo-view-screen';
|
||||||
static const String chatScreen = '/chat-screen';
|
static const String chatScreen = '/chat-screen';
|
||||||
static const String emptyChatScreen = '/empty-chat-screen';
|
static const String emptyChatScreen = '/empty-chat-screen';
|
||||||
|
static const String videoCallScreen = '/video-call-screen';
|
||||||
static const String glossaryScreen = '/glossary-screen';
|
static const String glossaryScreen = '/glossary-screen';
|
||||||
static const String blockedUsersScreen = '/blocked-users-screen';
|
static const String blockedUsersScreen = '/blocked-users-screen';
|
||||||
static const String notificationsSettingsScreen = '/notifications-settings-screen';
|
static const String notificationsSettingsScreen = '/notifications-settings-screen';
|
||||||
|
|||||||
@@ -10,161 +10,76 @@ class CustomTextStyles {
|
|||||||
static TextStyle get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
|
static TextStyle get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
|
||||||
|
|
||||||
// Title text style
|
// Title text style
|
||||||
static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(
|
static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(fontWeight: FontWeight.w600);
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black900);
|
||||||
color: appTheme.black900,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black900, fontSize: 13);
|
||||||
color: appTheme.black900,
|
|
||||||
fontSize: 13,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black);
|
||||||
color: appTheme.black,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black);
|
||||||
color: appTheme.black,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black);
|
||||||
color: appTheme.black,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.whiteA700);
|
||||||
color: appTheme.whiteA700,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black900, fontSize: 12);
|
||||||
color: appTheme.black900,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12);
|
||||||
color: appTheme.black,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12);
|
||||||
color: appTheme.black,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12);
|
||||||
color: appTheme.black,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.blueGray900, fontSize: 12);
|
||||||
color: appTheme.blueGray900,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.gray600);
|
||||||
color: appTheme.gray600,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(color: theme.colorScheme.primary, fontSize: 12);
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(
|
static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(color: appTheme.black900, fontSize: 12);
|
||||||
color: appTheme.black900,
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Label text style
|
// Label text style
|
||||||
static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(
|
static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(color: appTheme.gray60003);
|
||||||
color: appTheme.gray60003,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
|
static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(color: appTheme.whiteA700);
|
||||||
color: appTheme.whiteA700,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Title text style
|
// Title text style
|
||||||
static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith(fontSize: 16);
|
||||||
fontSize: 16,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(fontSize: 17, fontWeight: FontWeight.w900);
|
||||||
fontSize: 17,
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(color: appTheme.black900);
|
||||||
color: appTheme.black900,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600);
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(color: appTheme.whiteA700, fontWeight: FontWeight.w600);
|
||||||
color: appTheme.whiteA700,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(color: appTheme.blue800, fontWeight: FontWeight.w800);
|
||||||
color: appTheme.blue800,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(color: appTheme.black);
|
||||||
color: appTheme.black,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(color: appTheme.black, fontWeight: FontWeight.w700);
|
||||||
color: appTheme.black,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(color: appTheme.black, fontWeight: FontWeight.w800);
|
||||||
color: appTheme.black,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith(fontSize: 15, fontWeight: FontWeight.w700);
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(color: appTheme.greenA70001);
|
||||||
color: appTheme.greenA70001,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(color: appTheme.greenA70001, fontWeight: FontWeight.w800);
|
||||||
color: appTheme.greenA70001,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(color: appTheme.lightGreen300, fontWeight: FontWeight.w800);
|
||||||
color: appTheme.lightGreen300,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(color: appTheme.red200, fontWeight: FontWeight.w800);
|
||||||
color: appTheme.red200,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallSecondaryContainer =>
|
||||||
color: theme.colorScheme.secondaryContainer,
|
theme.textTheme.titleSmall!.copyWith(color: theme.colorScheme.secondaryContainer, fontWeight: FontWeight.w800);
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(color: theme.colorScheme.secondaryContainer);
|
||||||
color: theme.colorScheme.secondaryContainer,
|
|
||||||
);
|
|
||||||
|
|
||||||
static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(
|
static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(color: theme.colorScheme.onPrimary);
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on TextStyle {
|
extension on TextStyle {
|
||||||
@@ -178,81 +93,32 @@ extension on TextStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class TextStyles {
|
abstract class TextStyles {
|
||||||
static const headingXLSemibold = TextStyle(
|
static const headingXLSemibold = TextStyle(fontSize: 20, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const headingFS40FW600 = TextStyle(
|
static const headingFS40FW600 = TextStyle(fontSize: 40, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
fontSize: 40,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textLSemibold = TextStyle(
|
static const textLSemibold = TextStyle(fontSize: 18, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textLMedium = TextStyle(
|
static const textLMedium = TextStyle(fontSize: 18, fontWeight: FontWeight.w500, height: 1.255);
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textMSemibold = TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textMMedium = TextStyle(
|
static const textMSemibold = TextStyle(fontSize: 16, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textMMediumAuto = TextStyle(
|
static const textMMedium = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1);
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textSSemibold = TextStyle(
|
static const textMMediumAuto = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1.255);
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textSMedium = TextStyle(
|
static const textSSemibold = TextStyle(fontSize: 14, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textFS13FW400 = TextStyle(
|
static const textSMedium = TextStyle(fontSize: 14, fontWeight: FontWeight.w500, height: 1.255);
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textXSSemibold = TextStyle(
|
static const textFS13FW400 = TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1.255);
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const textFS10FW500 = TextStyle(
|
static const textXSSemibold = TextStyle(fontSize: 12, fontWeight: FontWeight.w500, height: 1.255);
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
static const textFS10FW500 = TextStyle(fontSize: 10, fontWeight: FontWeight.w500, height: 1.255);
|
||||||
height: 1.255,
|
|
||||||
);
|
static const textFS24FW700 = TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.255);
|
||||||
|
|
||||||
|
static const textFS22FW600 = TextStyle(fontSize: 22, fontWeight: FontWeight.w600, height: 1.255);
|
||||||
|
|
||||||
static const textFS24FW700 = TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
height: 1.255,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
|
|
||||||
/// Helper class for managing themes and colors.
|
/// Helper class for managing themes and colors.
|
||||||
class ThemeHelper {
|
class ThemeHelper {
|
||||||
// The current app theme
|
// The current app theme
|
||||||
final _appTheme = 'primary';
|
final _appTheme = 'primary';
|
||||||
|
|
||||||
// A map of custom color themes supported by the app
|
// A map of custom color themes supported by the app
|
||||||
final Map<String, PrimaryColors> _supportedCustomColor = {
|
final Map<String, PrimaryColors> _supportedCustomColor = {'primary': PrimaryColors()};
|
||||||
'primary': PrimaryColors(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// A map of color schemes supported by the app
|
// A map of color schemes supported by the app
|
||||||
final Map<String, ColorScheme> _supportedColorScheme = {
|
final Map<String, ColorScheme> _supportedColorScheme = {'primary': ColorSchemes.primaryColorScheme};
|
||||||
'primary': ColorSchemes.primaryColorScheme,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns the primary colors for the current theme.
|
/// Returns the primary colors for the current theme.
|
||||||
PrimaryColors _getThemeColors() {
|
PrimaryColors _getThemeColors() {
|
||||||
//throw exception to notify given theme is not found or not generated by the generator
|
//throw exception to notify given theme is not found or not generated by the generator
|
||||||
if (!_supportedCustomColor.containsKey(_appTheme)) {
|
if (!_supportedCustomColor.containsKey(_appTheme)) {
|
||||||
throw Exception(
|
throw Exception("$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner");
|
||||||
"$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
//return theme from map
|
//return theme from map
|
||||||
|
|
||||||
@@ -34,15 +27,14 @@ class ThemeHelper {
|
|||||||
ThemeData _getThemeData() {
|
ThemeData _getThemeData() {
|
||||||
//throw exception to notify given theme is not found or not generated by the generator
|
//throw exception to notify given theme is not found or not generated by the generator
|
||||||
if (!_supportedColorScheme.containsKey(_appTheme)) {
|
if (!_supportedColorScheme.containsKey(_appTheme)) {
|
||||||
throw Exception(
|
throw Exception("$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner");
|
||||||
"$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
//return theme from map
|
//return theme from map
|
||||||
|
|
||||||
var colorScheme = _supportedColorScheme[_appTheme] ?? ColorSchemes.primaryColorScheme;
|
var colorScheme = _supportedColorScheme[_appTheme] ?? ColorSchemes.primaryColorScheme;
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
textTheme: GoogleFonts.mulishTextTheme(),
|
fontFamily: 'Mulish-Local',
|
||||||
|
textTheme: ThemeData.light().textTheme.apply(fontFamily: 'Mulish-Local'),
|
||||||
visualDensity: VisualDensity.standard,
|
visualDensity: VisualDensity.standard,
|
||||||
colorScheme: colorScheme,
|
colorScheme: colorScheme,
|
||||||
scaffoldBackgroundColor: colorScheme.onPrimary,
|
scaffoldBackgroundColor: colorScheme.onPrimary,
|
||||||
@@ -60,17 +52,9 @@ class ThemeHelper {
|
|||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
side: BorderSide(
|
side: BorderSide(color: colorScheme.primary, width: 1),
|
||||||
color: colorScheme.primary,
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
width: 1,
|
visualDensity: const VisualDensity(vertical: -4, horizontal: -4),
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
vertical: -4,
|
|
||||||
horizontal: -4,
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -149,6 +133,7 @@ class PrimaryColors {
|
|||||||
Color get greenA70001 => const Color(0XFF00BE13);
|
Color get greenA70001 => const Color(0XFF00BE13);
|
||||||
|
|
||||||
Color get green => const Color(0XFF0BB450);
|
Color get green => const Color(0XFF0BB450);
|
||||||
|
Color get darkGreen => const Color(0XFF16A249);
|
||||||
|
|
||||||
Color get transparentGreen => const Color.fromARGB(12, 1, 154, 63);
|
Color get transparentGreen => const Color.fromARGB(12, 1, 154, 63);
|
||||||
|
|
||||||
@@ -184,6 +169,8 @@ class PrimaryColors {
|
|||||||
|
|
||||||
Color get lightGray0 => const Color(0xFFF6F6F6);
|
Color get lightGray0 => const Color(0xFFF6F6F6);
|
||||||
|
|
||||||
|
Color get darkGrey => const Color(0xFF5B656F);
|
||||||
|
|
||||||
Color get lightGray00 => const Color(0xFFE9E9EA);
|
Color get lightGray00 => const Color(0xFFE9E9EA);
|
||||||
|
|
||||||
Color get dottedBorder => const Color(0xFFE6E6E6);
|
Color get dottedBorder => const Color(0xFFE6E6E6);
|
||||||
@@ -210,6 +197,14 @@ class PrimaryColors {
|
|||||||
Color get cardNotificationsBG => const Color(0xFFA4A4A4);
|
Color get cardNotificationsBG => const Color(0xFFA4A4A4);
|
||||||
|
|
||||||
Color get grey7B => const Color(0xFF7B7B7B);
|
Color get grey7B => const Color(0xFF7B7B7B);
|
||||||
|
|
||||||
|
Color get greyD9 => const Color(0xFFD9D9D9);
|
||||||
|
|
||||||
|
Color get videoCallControlOffBackground => const Color(0xFFED2F2F);
|
||||||
|
|
||||||
|
Color get videoCallPipIconBackground => const Color(0xFF565656);
|
||||||
|
|
||||||
|
Color get videoCallNoticeBannerBackground => const Color(0xFF9D9D9D);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
abstract class TokenStorage {
|
abstract class TokenStorage {
|
||||||
|
static SharedPreferences? _cachedPrefs;
|
||||||
|
|
||||||
|
static Future<SharedPreferences> _getPrefs() async {
|
||||||
|
_cachedPrefs ??= await SharedPreferences.getInstance();
|
||||||
|
return _cachedPrefs!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the cached SharedPreferences instance.
|
||||||
|
/// This is primarily used for testing to ensure fresh state between tests.
|
||||||
|
@visibleForTesting
|
||||||
|
static void clearCache() {
|
||||||
|
_cachedPrefs = null;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<bool> haveTokenInStorage() async {
|
static Future<bool> haveTokenInStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
|
||||||
try {
|
try {
|
||||||
|
final sharedPreferences = await _getPrefs();
|
||||||
if (sharedPreferences.getString('token') == null) {
|
if (sharedPreferences.getString('token') == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -15,25 +30,25 @@ abstract class TokenStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> getTokenFromStorage() async {
|
static Future<String?> getTokenFromStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await _getPrefs();
|
||||||
return sharedPreferences.getString('token');
|
return sharedPreferences.getString('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> saveTokenInStorage(String token) async {
|
static Future<void> saveTokenInStorage(String token) async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await _getPrefs();
|
||||||
await sharedPreferences.setString('token', token);
|
await sharedPreferences.setString('token', token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> removeTokenInStorage() async {
|
static Future<void> removeTokenInStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await _getPrefs();
|
||||||
await sharedPreferences.remove('token');
|
await sharedPreferences.remove('token');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SocketIdStorage {
|
abstract class SocketIdStorage {
|
||||||
static Future<bool> haveSocketIdInStorage() async {
|
static Future<bool> haveSocketIdInStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
|
||||||
try {
|
try {
|
||||||
|
final sharedPreferences = await TokenStorage._getPrefs();
|
||||||
if (sharedPreferences.getString('socketId') == null) {
|
if (sharedPreferences.getString('socketId') == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -45,17 +60,17 @@ abstract class SocketIdStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> getSocketIdFromStorage() async {
|
static Future<String?> getSocketIdFromStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await TokenStorage._getPrefs();
|
||||||
return sharedPreferences.getString('socketId');
|
return sharedPreferences.getString('socketId');
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> saveSocketIdInStorage(String socketId) async {
|
static Future<void> saveSocketIdInStorage(String socketId) async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await TokenStorage._getPrefs();
|
||||||
await sharedPreferences.setString('socketId', socketId);
|
await sharedPreferences.setString('socketId', socketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> removeSocketIdInStorage() async {
|
static Future<void> removeSocketIdInStorage() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await TokenStorage._getPrefs();
|
||||||
await sharedPreferences.remove('socketId');
|
await sharedPreferences.remove('socketId');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
lib/core/utils/markdown_utils.dart
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/// Утилиты для обработки markdown текста
|
||||||
|
class MarkdownUtils {
|
||||||
|
/// Обрабатывает markdown текст:
|
||||||
|
/// - Исправляет неправильный синтаксис markdown ссылок
|
||||||
|
/// - Автоматически преобразует URL в markdown ссылки
|
||||||
|
/// - Преобразует **текст** в <b>текст</b> для надежного отображения жирного текста
|
||||||
|
static String processMarkdown(String text) {
|
||||||
|
// Сначала исправляем неправильный синтаксис markdown ссылок
|
||||||
|
// Например: [http://example.com](url) -> [http://example.com](http://example.com)
|
||||||
|
final wrongLinkRegex = RegExp(r'\[(https?://[^\]]+|www\.[^\]]+)\]\(([^\)]+)\)');
|
||||||
|
text = text.replaceAllMapped(wrongLinkRegex, (match) {
|
||||||
|
final urlInText = match.group(1)!;
|
||||||
|
if (!urlInText.startsWith('http://') && !urlInText.startsWith('https://') && !urlInText.startsWith('www.')) {
|
||||||
|
return match.group(0)!;
|
||||||
|
}
|
||||||
|
final fullUrl = urlInText.startsWith('www.') ? 'https://$urlInText' : urlInText;
|
||||||
|
return '[$urlInText]($fullUrl)';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Находим все существующие markdown ссылки и их позиции
|
||||||
|
final linkRegex = RegExp(r'\[([^\]]+)\]\(([^\)]+)\)');
|
||||||
|
final existingLinks = <_LinkRange>[];
|
||||||
|
for (final linkMatch in linkRegex.allMatches(text)) {
|
||||||
|
existingLinks.add(_LinkRange(linkMatch.start, linkMatch.end));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регулярное выражение для поиска URL (http://, https://, www.)
|
||||||
|
final urlRegex = RegExp(r'(https?://[^\s\)]+|www\.[^\s\)]+)', caseSensitive: false);
|
||||||
|
|
||||||
|
// Заменяем URL на markdown ссылки, если они еще не являются ссылками
|
||||||
|
String result = text;
|
||||||
|
int offset = 0;
|
||||||
|
for (final urlMatch in urlRegex.allMatches(text)) {
|
||||||
|
// Проверяем, не находится ли URL внутри существующей markdown ссылки
|
||||||
|
bool isInsideLink = existingLinks.any((link) =>
|
||||||
|
urlMatch.start >= link.start && urlMatch.end <= link.end);
|
||||||
|
if (!isInsideLink) {
|
||||||
|
final url = urlMatch.group(0)!;
|
||||||
|
final fullUrl = url.startsWith('www.') ? 'https://$url' : url;
|
||||||
|
final replacement = '[$url]($fullUrl)';
|
||||||
|
result = result.replaceRange(
|
||||||
|
urlMatch.start + offset,
|
||||||
|
urlMatch.end + offset,
|
||||||
|
replacement,
|
||||||
|
);
|
||||||
|
offset += replacement.length - url.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Гарантируем, что жирный текст будет распознан
|
||||||
|
// Заменяем **текст** на <b>текст</b> вручную для надежности
|
||||||
|
final boldRegex = RegExp(r'\*\*([^*]+)\*\*');
|
||||||
|
result = result.replaceAllMapped(boldRegex, (match) {
|
||||||
|
return '<b>${match.group(1)}</b>';
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Вспомогательный класс для хранения диапазона позиций ссылки
|
||||||
|
class _LinkRange {
|
||||||
|
final int start;
|
||||||
|
final int end;
|
||||||
|
|
||||||
|
_LinkRange(this.start, this.end);
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
extension Capitalization on String {
|
extension Capitalization on String {
|
||||||
String capitalize() {
|
String capitalize() {
|
||||||
|
if (isEmpty) return '';
|
||||||
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
|
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseGender() {
|
String parseGender() {
|
||||||
if (this == 'male') {
|
if (this == 'male') {
|
||||||
return 'Мужской';
|
return 'Мужской';
|
||||||
} else if (this == 'female') {
|
} else if (this == 'female') {
|
||||||
return 'Женский';
|
return 'Женский';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,21 @@ class ValidationUtils {
|
|||||||
return regex.hasMatch(phone);
|
return regex.hasMatch(phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Нормализация номера телефона (+79991234455)
|
||||||
|
static String normalizePhone(String phone) {
|
||||||
|
if (phone.isEmpty) return '';
|
||||||
|
final digits = phone.replaceAll(RegExp(r'[^\d]'), '');
|
||||||
|
if (digits.isEmpty) return '';
|
||||||
|
|
||||||
|
String normalized = digits;
|
||||||
|
if (digits.startsWith('8')) {
|
||||||
|
normalized = '7${digits.substring(1)}';
|
||||||
|
} else if (digits.length == 10) {
|
||||||
|
normalized = '7$digits';
|
||||||
|
}
|
||||||
|
return '+$normalized';
|
||||||
|
}
|
||||||
|
|
||||||
/// Валидация URL
|
/// Валидация URL
|
||||||
static bool isValidUrl(String url) {
|
static bool isValidUrl(String url) {
|
||||||
if (url.isEmpty) return false;
|
if (url.isEmpty) return false;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
import 'package:eatvio_app/core/views/cubit/views_state.dart';
|
import 'package:eatvio_app/core/views/cubit/views_state.dart';
|
||||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
@@ -86,9 +88,13 @@ class ViewsCubit extends Cubit<ViewsCubitState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
|
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
|
||||||
await viewsRepository.viewContent(
|
try {
|
||||||
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
|
await viewsRepository.viewContent(
|
||||||
);
|
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
|
||||||
|
);
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleDebouncedSend() {
|
void _scheduleDebouncedSend() {
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ class ViewsRepository {
|
|||||||
ViewsRepository({required this.viewsService});
|
ViewsRepository({required this.viewsService});
|
||||||
|
|
||||||
Future<void> viewContent({required ViewModel viewModel}) async {
|
Future<void> viewContent({required ViewModel viewModel}) async {
|
||||||
try {
|
await viewsService.viewContent(viewModel: viewModel);
|
||||||
await viewsService.viewContent(viewModel: viewModel);
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
@@ -8,12 +9,16 @@ import 'package:eatvio_app/core/const.dart';
|
|||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
|
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
|
|
||||||
class WsManager {
|
class WsManager {
|
||||||
final WsService wsService;
|
final WsService wsService;
|
||||||
|
final ErrorReporter _errorReporter;
|
||||||
|
|
||||||
WsManager({required this.wsService});
|
WsManager({required this.wsService, required ErrorReporter errorReporter})
|
||||||
|
: _errorReporter = errorReporter;
|
||||||
|
|
||||||
PusherChannelsClient? client;
|
PusherChannelsClient? client;
|
||||||
int? userId;
|
int? userId;
|
||||||
@@ -22,6 +27,10 @@ class WsManager {
|
|||||||
PrivateChannel? _privateMessagesChannel;
|
PrivateChannel? _privateMessagesChannel;
|
||||||
PrivateChannel? _privateDirectChatChannel;
|
PrivateChannel? _privateDirectChatChannel;
|
||||||
|
|
||||||
|
StreamSubscription? _lifecycleSubscription;
|
||||||
|
StreamSubscription? _eventSubscription;
|
||||||
|
StreamSubscription? _connectionEstablishedSubscription;
|
||||||
|
|
||||||
Stream<ChannelReadEvent>? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
|
Stream<ChannelReadEvent>? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
|
||||||
|
|
||||||
Stream<ChannelReadEvent>? get newNotificationsStream => _privateNotificationChannel?.bind('Illuminate\\Notifications\\Events\\BroadcastNotificationCreated');
|
Stream<ChannelReadEvent>? get newNotificationsStream => _privateNotificationChannel?.bind('Illuminate\\Notifications\\Events\\BroadcastNotificationCreated');
|
||||||
@@ -33,7 +42,7 @@ class WsManager {
|
|||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
try {
|
try {
|
||||||
await TokenStorage.haveTokenInStorage();
|
if (!await TokenStorage.haveTokenInStorage()) return;
|
||||||
final wsUrl = await wsService.getWebsocket();
|
final wsUrl = await wsService.getWebsocket();
|
||||||
|
|
||||||
final options = PusherChannelsOptions.fromHost(
|
final options = PusherChannelsOptions.fromHost(
|
||||||
@@ -71,7 +80,7 @@ class WsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client?.eventStream.listen((event) async {
|
_connectionEstablishedSubscription = client?.eventStream.listen((event) async {
|
||||||
if (event.name == 'pusher:connection_established') {
|
if (event.name == 'pusher:connection_established') {
|
||||||
final data = event.data;
|
final data = event.data;
|
||||||
final socketId = extractSocketId(data);
|
final socketId = extractSocketId(data);
|
||||||
@@ -87,26 +96,46 @@ class WsManager {
|
|||||||
await _initPrivateNotificationsChannel();
|
await _initPrivateNotificationsChannel();
|
||||||
await _initPrivateMessagesChannel();
|
await _initPrivateMessagesChannel();
|
||||||
|
|
||||||
client?.lifecycleStream.listen((data) {
|
_lifecycleSubscription = client?.lifecycleStream.listen((data) {
|
||||||
if (data == PusherChannelsClientLifeCycleState.establishedConnection) {
|
if (data == PusherChannelsClientLifeCycleState.establishedConnection) {
|
||||||
_unsubscribe();
|
_unsubscribe();
|
||||||
_subscribe();
|
_subscribe();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
client?.eventStream.listen((event) {
|
_eventSubscription = client?.eventStream.listen((event) {
|
||||||
EasyDebounce.debounce(
|
log('WsManager received event: ${event.name}');
|
||||||
'searchChatRoom',
|
// Только если это не системные сообщения Pusher, обновляем список чатов
|
||||||
const Duration(milliseconds: 300),
|
if (!event.name.startsWith('pusher:')) {
|
||||||
() => sl<ChatsCubit>().getChatRooms(),
|
EasyDebounce.debounce(
|
||||||
);
|
'searchChatRoom',
|
||||||
|
const Duration(milliseconds: 300),
|
||||||
|
() => sl<ChatsCubit>().getChatRooms(),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_subscribe();
|
_subscribe();
|
||||||
|
await _connectCubitsToStreams();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
await _errorReporter.report(e, st, origin: 'WsManager');
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Подключает ChatsCubit и NotificationsCubit к стримам сокета.
|
||||||
|
/// Вызывается после успешного initialize(), чтобы уведомления и чаты работали
|
||||||
|
/// и при входе уже залогиненного пользователя (редирект на feed), не только после экрана логина.
|
||||||
|
Future<void> _connectCubitsToStreams() async {
|
||||||
|
try {
|
||||||
|
await sl<ChatsCubit>().connectToWebSocket();
|
||||||
|
await sl<NotificationsCubit>().getNotifications();
|
||||||
|
await sl<NotificationsCubit>().connectToWebSocket();
|
||||||
|
} catch (e, st) {
|
||||||
|
log('WsManager._connectCubitsToStreams: $e', stackTrace: st);
|
||||||
|
await _errorReporter.report(e, st, origin: 'WsManager');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initPrivateNotificationsChannel() async {
|
Future<void> _initPrivateNotificationsChannel() async {
|
||||||
final token = await TokenStorage.getTokenFromStorage();
|
final token = await TokenStorage.getTokenFromStorage();
|
||||||
_privateNotificationChannel = client?.privateChannel(
|
_privateNotificationChannel = client?.privateChannel(
|
||||||
@@ -166,6 +195,12 @@ class WsManager {
|
|||||||
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
await client?.disconnect();
|
await client?.disconnect();
|
||||||
|
await _lifecycleSubscription?.cancel();
|
||||||
|
await _eventSubscription?.cancel();
|
||||||
|
await _connectionEstablishedSubscription?.cancel();
|
||||||
|
_lifecycleSubscription = null;
|
||||||
|
_eventSubscription = null;
|
||||||
|
_connectionEstablishedSubscription = null;
|
||||||
client = null;
|
client = null;
|
||||||
userId = null;
|
userId = null;
|
||||||
_privateDirectChatChannel?.unsubscribe();
|
_privateDirectChatChannel?.unsubscribe();
|
||||||
|
|||||||
@@ -3,16 +3,28 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class CustomDropDownButton<T> extends StatelessWidget {
|
class CustomDropDownButton<T> extends StatelessWidget {
|
||||||
const CustomDropDownButton({super.key, required this.items, required this.onChanged, this.value, this.hint, this.hasError = false});
|
const CustomDropDownButton({
|
||||||
|
super.key,
|
||||||
|
required this.items,
|
||||||
|
required this.onChanged,
|
||||||
|
this.value,
|
||||||
|
this.hint,
|
||||||
|
this.selectedItemBuilder,
|
||||||
|
this.hasError = false,
|
||||||
|
this.isExpanded = false,
|
||||||
|
});
|
||||||
final T? value;
|
final T? value;
|
||||||
final List<DropdownMenuItem<T>> items;
|
final List<DropdownMenuItem<T>> items;
|
||||||
final void Function(T?)? onChanged;
|
final void Function(T?)? onChanged;
|
||||||
final Widget? hint;
|
final Widget? hint;
|
||||||
final bool hasError;
|
final bool hasError;
|
||||||
|
final List<Widget> Function(BuildContext)? selectedItemBuilder;
|
||||||
|
final bool isExpanded;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DropdownButtonFormField(
|
return DropdownButtonFormField(
|
||||||
hint: hint,
|
hint: hint,
|
||||||
|
isExpanded: isExpanded,
|
||||||
icon: SvgPicture.asset('assets/icons/arrow_down2.svg', width: 12, height: 8),
|
icon: SvgPicture.asset('assets/icons/arrow_down2.svg', width: 12, height: 8),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
dropdownColor: appTheme.lightGray0,
|
dropdownColor: appTheme.lightGray0,
|
||||||
@@ -35,6 +47,7 @@ class CustomDropDownButton<T> extends StatelessWidget {
|
|||||||
initialValue: value,
|
initialValue: value,
|
||||||
items: items,
|
items: items,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
|
selectedItemBuilder: selectedItemBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class CustomOutlinedButton extends BaseButton {
|
|||||||
margin: margin,
|
margin: margin,
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
style: buttonStyle!,
|
style: buttonStyle ?? OutlinedButton.styleFrom(),
|
||||||
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
|
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -59,7 +59,7 @@ class CustomOutlinedButton extends BaseButton {
|
|||||||
margin: margin,
|
margin: margin,
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
style: buttonStyle!,
|
style: buttonStyle ?? OutlinedButton.styleFrom(),
|
||||||
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
|
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class FeedIconButton extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (svgIcon != null) svgIcon!,
|
?svgIcon,
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
|
|||||||
@@ -12,15 +12,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class InfoRow extends StatelessWidget {
|
class InfoRow extends StatelessWidget {
|
||||||
const InfoRow({
|
const InfoRow({required this.type, super.key, this.diet, this.itemsBackgroundColor, this.disableDescriptionOfTypes = false, this.plan, this.recipe});
|
||||||
required this.type,
|
|
||||||
super.key,
|
|
||||||
this.diet,
|
|
||||||
this.itemsBackgroundColor,
|
|
||||||
this.disableDescriptionOfTypes = false,
|
|
||||||
this.plan,
|
|
||||||
this.recipe,
|
|
||||||
});
|
|
||||||
final ProductType type;
|
final ProductType type;
|
||||||
final Color? itemsBackgroundColor;
|
final Color? itemsBackgroundColor;
|
||||||
final Diet? diet;
|
final Diet? diet;
|
||||||
@@ -50,44 +42,13 @@ class InfoRow extends StatelessWidget {
|
|||||||
InfoBouble(
|
InfoBouble(
|
||||||
backgroundColor: itemsBackgroundColor,
|
backgroundColor: itemsBackgroundColor,
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset('assets/icons/time2.svg', width: 13.3, height: 13.3),
|
||||||
'assets/icons/time2.svg',
|
|
||||||
width: 13.3,
|
|
||||||
height: 13.3,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(switch (type) {
|
||||||
switch (type) {
|
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
||||||
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
|
||||||
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
|
ProductType.recipe => timeTitle ?? '',
|
||||||
ProductType.recipe => timeTitle ?? '',
|
}, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||||
},
|
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
InfoBouble(
|
|
||||||
backgroundColor: itemsBackgroundColor,
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(
|
|
||||||
switch (type) {
|
|
||||||
ProductType.diet => 'assets/icons/people.svg',
|
|
||||||
ProductType.plan => 'assets/icons/people.svg',
|
|
||||||
ProductType.recipe => 'assets/icons/calories.svg',
|
|
||||||
},
|
|
||||||
width: 13.3,
|
|
||||||
height: 13.3,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
switch (type) {
|
|
||||||
ProductType.diet => '80 человек прошли',
|
|
||||||
ProductType.plan => '80 человек прошли',
|
|
||||||
ProductType.recipe => '${recipe?.calories.toStringAsFixed(0) ?? '0'} ккал',
|
|
||||||
},
|
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -95,11 +56,17 @@ class InfoRow extends StatelessWidget {
|
|||||||
InfoBouble(
|
InfoBouble(
|
||||||
backgroundColor: itemsBackgroundColor,
|
backgroundColor: itemsBackgroundColor,
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset('assets/icons/calories.svg', width: 13.3, height: 13.3),
|
||||||
'assets/icons/steps.svg',
|
const SizedBox(width: 4),
|
||||||
width: 13.3,
|
Text('${(recipe?.calories ?? 0).toStringAsFixed(0)} ккал', style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||||
height: 13.3,
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
if (type == ProductType.recipe)
|
||||||
|
InfoBouble(
|
||||||
|
backgroundColor: itemsBackgroundColor,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset('assets/icons/steps.svg', width: 13.3, height: 13.3),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
|
'${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
|
||||||
@@ -110,13 +77,7 @@ class InfoRow extends StatelessWidget {
|
|||||||
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
|
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
|
||||||
InfoBouble(
|
InfoBouble(
|
||||||
backgroundColor: itemsBackgroundColor,
|
backgroundColor: itemsBackgroundColor,
|
||||||
children: [
|
children: [TypeBadges(type: diet?.type, planType: plan?.type, disableDescriptionOfTypes: disableDescriptionOfTypes)],
|
||||||
TypeBadges(
|
|
||||||
type: diet?.type,
|
|
||||||
planType: plan?.type,
|
|
||||||
disableDescriptionOfTypes: disableDescriptionOfTypes,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||