Compare commits
22 Commits
e7d2154d98
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
| 1323de9f79 | |||
| 0a6c40181a | |||
| fcb23e0d5f | |||
| 11c00ad2cc | |||
| 419b54d5dd | |||
| a6e8b9d070 | |||
| 4a919a5bcc | |||
| e91f373258 | |||
| 902c6d27a4 | |||
| 98c5a7a1d1 | |||
| 9eda119527 | |||
| 939b5be7d8 | |||
| 10c21e4ae3 | |||
| 406b2bc2dc | |||
| b9da1d3064 | |||
| 0b6757e26f | |||
| 134ffdde60 | |||
| bb49bbd0f7 | |||
| 7779803ccc | |||
| 591265a7fc | |||
| c996d0847f | |||
| 713cc2520b |
@@ -44,7 +44,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.sparkit.be_happy"
|
applicationId = "com.sparkit.behappy"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent>
|
</intent>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -49,6 +50,14 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:label="card_binding_deep_link">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="com.behappy.app" android:host="card-result" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 26 KiB |
@@ -2,3 +2,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
yandexMapkit.variant=full
|
yandexMapkit.variant=full
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
|
|||||||
BIN
assets/fonts/DigitalNumbers-Regular.ttf
Normal file
BIN
assets/icons/card_result_screen.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
assets/icons/failed.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/icons/qr_icon_order.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
assets/icons/scooter_reserved_placemark_fill.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/icons/success.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
assets/icons/timer.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/news_def.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
assets/splash_logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/splash_map.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
3
devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
@@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>12.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
|
#include? "Secrets.xcconfig"
|
||||||
|
YANDEX_MAPKIT_API_KEY=a0ef1404-2650-4f28-9891-c965ecc09174
|
||||||
|
// Переопределение Pods/ML Kit: arm64 для симулятора на Apple Silicon (M1/M4)
|
||||||
|
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||||
|
|||||||
5
ios/Flutter/Profile.xcconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
|
#include? "Secrets.xcconfig"
|
||||||
|
YANDEX_MAPKIT_API_KEY=a0ef1404-2650-4f28-9891-c965ecc09174
|
||||||
|
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||||
@@ -1 +1,5 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
|
#include? "Secrets.xcconfig"
|
||||||
|
YANDEX_MAPKIT_API_KEY=a0ef1404-2650-4f28-9891-c965ecc09174
|
||||||
|
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||||
|
|||||||
2
ios/Flutter/Secrets.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Локальные переопределения (опционально). Не задавайте YANDEX_MAPKIT_API_KEY плейсхолдером —
|
||||||
|
// рабочий ключ задаётся в конце Debug/Release/Profile.xcconfig после этого include.
|
||||||
3
ios/Flutter/Secrets.xcconfig.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Скопируй в ios/Flutter/Secrets.xcconfig (файл в .gitignore) для своих локальных xcconfig-переменных.
|
||||||
|
// Ключ MapKit задаётся в конце Debug/Release/Profile.xcconfig — не подставляйте туда тестовый плейсхолдер вместо реального ключа.
|
||||||
|
// https://developer.tech.yandex.ru/
|
||||||
48
ios/Podfile
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# image_picker_ios и др. требуют минимум iOS 13
|
||||||
|
platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
# ML Kit исключает arm64 для симулятора → сборка только x86_64.
|
||||||
|
# На M1 это ещё могло работать через Rosetta; на M4 симулятор только arm64.
|
||||||
|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'i386'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
81
ios/Podfile.lock
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
PODS:
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- flutter_secure_storage (6.0.0):
|
||||||
|
- Flutter
|
||||||
|
- geolocator_apple (1.2.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- image_picker_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- mobile_scanner (7.0.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- yandex_mapkit (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- YandexMapsMobile (= 4.22.0-lite)
|
||||||
|
- YandexMapsMobile (4.22.0-lite)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||||
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- yandex_mapkit (from `.symlinks/plugins/yandex_mapkit/ios`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- YandexMapsMobile
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
device_info_plus:
|
||||||
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
flutter_secure_storage:
|
||||||
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
geolocator_apple:
|
||||||
|
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
mobile_scanner:
|
||||||
|
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
yandex_mapkit:
|
||||||
|
:path: ".symlinks/plugins/yandex_mapkit/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
|
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||||
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
yandex_mapkit: bac34ca1bdf97e87252a8c6d09a95b1fe39ba103
|
||||||
|
YandexMapsMobile: c73844c6096bbb240d1491adc511c2ef4a88d88d
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 715b1068ab8815ca4115a24e305e836709652b69
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
23
ios/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Запуск на симуляторе (Mac)
|
||||||
|
|
||||||
|
**Терминал** (⌘ + `):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/commercesparkit.by/Desktop/projekt/be_happy_public
|
||||||
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||||
|
open -a Simulator
|
||||||
|
flutter run
|
||||||
|
```
|
||||||
|
|
||||||
|
Первый раз или после обновления кода:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/commercesparkit.by/Desktop/projekt/be_happy_public
|
||||||
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||||
|
flutter pub get
|
||||||
|
cd ios && pod install && cd ..
|
||||||
|
open -a Simulator
|
||||||
|
flutter run
|
||||||
|
```
|
||||||
|
|
||||||
|
Важно: команды только из папки `be_happy_public`, не из `~`.
|
||||||
@@ -10,10 +10,12 @@
|
|||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
6D33173F8CCB65895D564988 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB22D9E6003D7254B27B141 /* Pods_RunnerTests.framework */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
FB3C1019114E649A3758FC7D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 151F40E0A8A0B88D7F9715D8 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -40,14 +42,20 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0EB91AA8815BC4C23E9C769C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
151F40E0A8A0B88D7F9715D8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
4BB22D9E6003D7254B27B141 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
4D0302F803C0949E93DDCE44 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
525F1415727A8E2A6F5CA828 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB41CF90195004384FC /* Profile.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Profile.xcconfig; path = Flutter/Profile.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -55,13 +63,25 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
BAF1C86965EF66AE2F0839BD /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BE9A8C77364E79EA4C6EE36B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D24CB1920C4748F67C854B22 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
83F4E53737A5B4793588BFEB /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
6D33173F8CCB65895D564988 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FB3C1019114E649A3758FC7D /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -82,6 +102,7 @@
|
|||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB41CF90195004384FC /* Profile.xcconfig */,
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Flutter;
|
name = Flutter;
|
||||||
@@ -94,6 +115,8 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
991E98147265C7C44D711731 /* Pods */,
|
||||||
|
E94B50B8FA90842D98EF918A /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -121,6 +144,29 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
991E98147265C7C44D711731 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4D0302F803C0949E93DDCE44 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
0EB91AA8815BC4C23E9C769C /* Pods-Runner.release.xcconfig */,
|
||||||
|
525F1415727A8E2A6F5CA828 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
BAF1C86965EF66AE2F0839BD /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
D24CB1920C4748F67C854B22 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
BE9A8C77364E79EA4C6EE36B /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E94B50B8FA90842D98EF918A /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
151F40E0A8A0B88D7F9715D8 /* Pods_Runner.framework */,
|
||||||
|
4BB22D9E6003D7254B27B141 /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -128,8 +174,10 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
1D742F73AAFC432DF3836D7E /* [CP] Check Pods Manifest.lock */,
|
||||||
331C807D294A63A400263BE5 /* Sources */,
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
331C807F294A63A400263BE5 /* Resources */,
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
83F4E53737A5B4793588BFEB /* Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -145,12 +193,15 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
8734FCBECF65F25CB259DF82 /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
25F6FF3856E913FFAF963081 /* [CP] Embed Pods Frameworks */,
|
||||||
|
FF12327738E91E5AA87207B8 /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -222,6 +273,45 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
1D742F73AAFC432DF3836D7E /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
25F6FF3856E913FFAF963081 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -238,6 +328,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
};
|
};
|
||||||
|
8734FCBECF65F25CB259DF82 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -253,6 +365,23 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
FF12327738E91E5AA87207B8 /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -346,7 +475,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -357,7 +486,7 @@
|
|||||||
};
|
};
|
||||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 9740EEB41CF90195004384FC /* Profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
@@ -378,6 +507,7 @@
|
|||||||
};
|
};
|
||||||
331C8088294A63A400263BE5 /* Debug */ = {
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BAF1C86965EF66AE2F0839BD /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@@ -395,6 +525,7 @@
|
|||||||
};
|
};
|
||||||
331C8089294A63A400263BE5 /* Release */ = {
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = D24CB1920C4748F67C854B22 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@@ -410,6 +541,7 @@
|
|||||||
};
|
};
|
||||||
331C808A294A63A400263BE5 /* Profile */ = {
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BE9A8C77364E79EA4C6EE36B /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@@ -427,7 +559,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
@@ -472,7 +604,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -484,7 +616,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
@@ -523,7 +655,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|||||||
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@@ -4,4 +4,7 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import YandexMapsMobile
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
let apiKey =
|
||||||
|
(Bundle.main.object(forInfoDictionaryKey: "YandexMapKitApiKey") as? String ?? "")
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
#if DEBUG
|
||||||
|
if apiKey.isEmpty || apiKey.contains("$(") {
|
||||||
|
assertionFailure(
|
||||||
|
"YandexMapKitApiKey пустой или не подставился из xcconfig — проверь YANDEX_MAPKIT_API_KEY в ios/Flutter/*.xcconfig"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
YMKMapKit.setLocale(Locale.current.identifier)
|
||||||
|
YMKMapKit.setApiKey(apiKey)
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,122 +1 @@
|
|||||||
{
|
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "83.5x83.5",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "1024x1024",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 351 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 21 KiB |
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@@ -24,6 +26,29 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -41,9 +66,7 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>YandexMapKitApiKey</key>
|
||||||
<true/>
|
<string>$(YANDEX_MAPKIT_API_KEY)</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import '../../domain/entities/client_notification.dart';
|
import '../../domain/entities/client_notification.dart';
|
||||||
|
|
||||||
class ClientNotificationDto {
|
class ClientNotificationDto {
|
||||||
@@ -43,12 +42,19 @@ class ClientNotificationDto {
|
|||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
type: _parseType(type),
|
type: _parseType(type),
|
||||||
category: _parseCategory(category),
|
category: _parseCategory(category),
|
||||||
createdAt: DateTime.parse(createdAt),
|
createdAt: _parseUtcDate(createdAt),
|
||||||
canceledAt: canceledAt != null ? DateTime.parse(canceledAt!) : null,
|
canceledAt: canceledAt != null ? _parseUtcDate(canceledAt!) : null,
|
||||||
readAt: readAt != null ? DateTime.parse(readAt!) : null,
|
readAt: readAt != null ? _parseUtcDate(readAt!) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateTime _parseUtcDate(String dateString) {
|
||||||
|
if (dateString.endsWith('Z') || dateString.contains('+') || dateString.contains('-')) {
|
||||||
|
return DateTime.parse(dateString).toLocal();
|
||||||
|
}
|
||||||
|
return DateTime.parse('${dateString}Z').toLocal();
|
||||||
|
}
|
||||||
|
|
||||||
NotificationType _parseType(String type) {
|
NotificationType _parseType(String type) {
|
||||||
switch (type.toLowerCase()) {
|
switch (type.toLowerCase()) {
|
||||||
case 'info':
|
case 'info':
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import '../../domain/entities/payment_card.dart';
|
|||||||
|
|
||||||
class PaymentCardResponseDto {
|
class PaymentCardResponseDto {
|
||||||
final int id;
|
final int id;
|
||||||
final int clientId;
|
final String bindingId;
|
||||||
final int expirationMonth;
|
final int expirationMonth;
|
||||||
final int expirationYear;
|
final int expirationYear;
|
||||||
final String cardHolder;
|
final String cardHolder;
|
||||||
@@ -12,7 +12,7 @@ class PaymentCardResponseDto {
|
|||||||
|
|
||||||
PaymentCardResponseDto({
|
PaymentCardResponseDto({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.clientId,
|
required this.bindingId,
|
||||||
required this.expirationMonth,
|
required this.expirationMonth,
|
||||||
required this.expirationYear,
|
required this.expirationYear,
|
||||||
required this.cardHolder,
|
required this.cardHolder,
|
||||||
@@ -24,7 +24,7 @@ class PaymentCardResponseDto {
|
|||||||
factory PaymentCardResponseDto.fromJson(Map<String, dynamic> json) {
|
factory PaymentCardResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
return PaymentCardResponseDto(
|
return PaymentCardResponseDto(
|
||||||
id: json['id'] as int,
|
id: json['id'] as int,
|
||||||
clientId: json['clientId'] as int,
|
bindingId: json['bindingId'] as String,
|
||||||
expirationMonth: json['expirationMonth'] as int,
|
expirationMonth: json['expirationMonth'] as int,
|
||||||
expirationYear: json['expirationYear'] as int,
|
expirationYear: json['expirationYear'] as int,
|
||||||
cardHolder: json['cardHolder'] as String,
|
cardHolder: json['cardHolder'] as String,
|
||||||
@@ -37,13 +37,9 @@ class PaymentCardResponseDto {
|
|||||||
PaymentCard toEntity(String? fullCardNumber) {
|
PaymentCard toEntity(String? fullCardNumber) {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: id,
|
id: id,
|
||||||
clientId: clientId,
|
bindingId: bindingId,
|
||||||
expirationMonth: expirationMonth,
|
|
||||||
expirationYear: expirationYear,
|
|
||||||
cardHolder: cardHolder,
|
|
||||||
cardLastNumber: cardLastNumber,
|
cardLastNumber: cardLastNumber,
|
||||||
isMain: isMain,
|
isMain: isMain,
|
||||||
type: type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
lib/data/models/promo_code_response_dto.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import '../../domain/entities/promo_code_result.dart';
|
||||||
|
|
||||||
|
class PromoCodeResponseDto {
|
||||||
|
final bool success;
|
||||||
|
final double balance;
|
||||||
|
|
||||||
|
PromoCodeResponseDto({
|
||||||
|
required this.success,
|
||||||
|
required this.balance,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PromoCodeResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PromoCodeResponseDto(
|
||||||
|
success: json['success'] as bool,
|
||||||
|
balance: (json['balance'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PromoCodeResult toEntity() {
|
||||||
|
return PromoCodeResult(
|
||||||
|
success: success,
|
||||||
|
balance: balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import 'package:path/path.dart';
|
|||||||
import 'package:flutter_client_sse/constants/sse_request_type_enum.dart';
|
import 'package:flutter_client_sse/constants/sse_request_type_enum.dart';
|
||||||
import 'package:flutter_client_sse/flutter_client_sse.dart';
|
import 'package:flutter_client_sse/flutter_client_sse.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/client_subscription.dart';
|
||||||
import '../../domain/entities/point.dart';
|
import '../../domain/entities/point.dart';
|
||||||
import '../../domain/entities/user_profile.dart';
|
import '../../domain/entities/user_profile.dart';
|
||||||
import '../../domain/entities/payment_card.dart';
|
import '../../domain/entities/payment_card.dart';
|
||||||
@@ -51,7 +52,11 @@ class ApiService {
|
|||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
"$baseUrl/auth/client/login",
|
"$baseUrl/auth/client/login",
|
||||||
data: {"phone": phone, "model": model, "systemId": systemId},
|
data: {
|
||||||
|
"phone": phone,
|
||||||
|
"model": model,
|
||||||
|
"systemId": systemId
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
@@ -137,15 +142,17 @@ class ApiService {
|
|||||||
if (avatarId != null && profileData["avatar"] != null) {
|
if (avatarId != null && profileData["avatar"] != null) {
|
||||||
final String? avatarPath = profileData["avatar"]["path"];
|
final String? avatarPath = profileData["avatar"]["path"];
|
||||||
if (avatarPath != null && avatarPath.isNotEmpty) {
|
if (avatarPath != null && avatarPath.isNotEmpty) {
|
||||||
avatarUrl = Uri.parse(fileBaseUrl).resolve(avatarPath).toString(); }
|
avatarUrl = Uri.parse(fileBaseUrl).resolve(avatarPath).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic balanceRaw = profileData["balance"];
|
dynamic balanceRaw = profileData["balance"];
|
||||||
int? balance;
|
double? balance;
|
||||||
if (balanceRaw is int) {
|
|
||||||
balance = balanceRaw;
|
if (balanceRaw is num) {
|
||||||
|
balance = balanceRaw.toDouble();
|
||||||
} else if (balanceRaw is String) {
|
} else if (balanceRaw is String) {
|
||||||
balance = int.tryParse(balanceRaw);
|
balance = double.tryParse(balanceRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserProfile(
|
return UserProfile(
|
||||||
@@ -175,13 +182,9 @@ class ApiService {
|
|||||||
cardsJson.map((cardJson) async {
|
cardsJson.map((cardJson) async {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: cardJson['id'],
|
id: cardJson['id'],
|
||||||
clientId: cardJson['clientId'],
|
bindingId: cardJson['bindingId'],
|
||||||
expirationMonth: cardJson['expirationMonth'],
|
|
||||||
expirationYear: cardJson['expirationYear'],
|
|
||||||
cardHolder: cardJson['cardHolder'],
|
|
||||||
cardLastNumber: cardJson['cardLastNumber'],
|
cardLastNumber: cardJson['cardLastNumber'],
|
||||||
isMain: cardJson['isMain'],
|
isMain: cardJson['isMain'],
|
||||||
type: cardJson['cardType'],
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -236,11 +239,11 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamic balanceRaw = data["balance"];
|
dynamic balanceRaw = data["balance"];
|
||||||
int? balance;
|
double? balance;
|
||||||
if (balanceRaw is int) {
|
if (balanceRaw is double) {
|
||||||
balance = balanceRaw;
|
balance = balanceRaw;
|
||||||
} else if (balanceRaw is String) {
|
} else if (balanceRaw is String) {
|
||||||
balance = int.tryParse(balanceRaw);
|
balance = double.tryParse(balanceRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserProfile(
|
return UserProfile(
|
||||||
@@ -394,10 +397,7 @@ class ApiService {
|
|||||||
final url = "$baseUrl/scooter/$title/code";
|
final url = "$baseUrl/scooter/$title/code";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(url, options: await _getAuthOptions());
|
||||||
url,
|
|
||||||
options: await _getAuthOptions(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return Scooter.fromJson(response.data);
|
return Scooter.fromJson(response.data);
|
||||||
@@ -421,7 +421,7 @@ class ApiService {
|
|||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 500,
|
int pageSize = 500,
|
||||||
}) async {
|
}) async {
|
||||||
const path = "$baseUrl/zone/available";
|
const path = "$baseUrl/zone/available2";
|
||||||
|
|
||||||
final queryParams = {
|
final queryParams = {
|
||||||
'readAll': 'true',
|
'readAll': 'true',
|
||||||
@@ -463,7 +463,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Subscription>> getClientSubscriptions() async {
|
Future<List<ClientSubscription>> getClientSubscriptions() async {
|
||||||
const url = "$baseUrl/scootersubscription/client";
|
const url = "$baseUrl/scootersubscription/client";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -474,14 +474,7 @@ class ApiService {
|
|||||||
final List<dynamic> items = responseData['data'] ?? [];
|
final List<dynamic> items = responseData['data'] ?? [];
|
||||||
|
|
||||||
return items.map((item) {
|
return items.map((item) {
|
||||||
final Map<String, dynamic> subscriptionMap =
|
return ClientSubscription.fromJson(item);
|
||||||
Map<String, dynamic>.from(item['subscription'] ?? {});
|
|
||||||
|
|
||||||
if (item['expiredAt'] != null) {
|
|
||||||
subscriptionMap['activeTo'] = item['expiredAt'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Subscription.fromJson(subscriptionMap);
|
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -525,13 +518,13 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> activateSubscription({required int optionId}) async {
|
Future<bool> activateSubscription({required int optionId, required int cardId}) async {
|
||||||
const url = "$baseUrl/scootersubscription/client";
|
const url = "$baseUrl/scootersubscription/client";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
url,
|
url,
|
||||||
data: {"optionId": optionId},
|
data: {"optionId": optionId, "cardId": cardId},
|
||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -547,32 +540,27 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> addPaymentCard({
|
Future<Map<String, dynamic>> addPaymentCard() async {
|
||||||
required String cardNumber,
|
|
||||||
required String cardHolder,
|
|
||||||
required int expirationMonth,
|
|
||||||
required int expirationYear,
|
|
||||||
required String cvv,
|
|
||||||
}) async {
|
|
||||||
const url = "$baseUrl/client/card";
|
const url = "$baseUrl/client/card";
|
||||||
final cleanCardNumber = cardNumber.replaceAll(' ', '');
|
/*final cleanCardNumber = cardNumber.replaceAll(' ', '');*/
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
url,
|
url,
|
||||||
data: {
|
/*data: {
|
||||||
"cardNumber": cleanCardNumber,
|
"cardNumber": cleanCardNumber,
|
||||||
"cardHolder": cardHolder,
|
"cardHolder": cardHolder,
|
||||||
"expirationMonth": expirationMonth,
|
"expirationMonth": expirationMonth,
|
||||||
"expirationYear": expirationYear,
|
"expirationYear": expirationYear,
|
||||||
"cvv": cvv,
|
"cvv": cvv,
|
||||||
},
|
},*/
|
||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return response.data['id'] as int;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw AuthException('Непредвиденный статус: ${response.statusCode}', 0);
|
throw AuthException('Непредвиденный статус: ${response.statusCode}', 0);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final data = e.response?.data;
|
final data = e.response?.data;
|
||||||
@@ -674,7 +662,9 @@ class ApiService {
|
|||||||
final firstError = data['message'][0]['message'].toString();
|
final firstError = data['message'][0]['message'].toString();
|
||||||
|
|
||||||
if (firstError.contains("Wrong start zone")) {
|
if (firstError.contains("Wrong start zone")) {
|
||||||
throw WrongZoneException(message: "Некорректная зона для начала поездки.");
|
throw WrongZoneException(
|
||||||
|
message: "Некорректная зона для начала поездки.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,7 +695,9 @@ class ApiService {
|
|||||||
final firstError = data['message'][0]['message'].toString();
|
final firstError = data['message'][0]['message'].toString();
|
||||||
|
|
||||||
if (firstError.contains("Wrong start zone")) {
|
if (firstError.contains("Wrong start zone")) {
|
||||||
throw WrongZoneException(message: "Некорректная зона для начала поездки.");
|
throw WrongZoneException(
|
||||||
|
message: "Некорректная зона для начала поездки.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,17 +709,13 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ScooterOrder?> cancelRide(int orderId) async {
|
Future<void> cancelRide(int orderId) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.put(
|
final response = await _dio.put(
|
||||||
"$baseUrl/scooterorder/$orderId/cancel",
|
"$baseUrl/scooterorder/$orderId/cancel",
|
||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
||||||
return ScooterOrder.fromJson(response.data);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
_handleDioError(e);
|
_handleDioError(e);
|
||||||
return null;
|
return null;
|
||||||
@@ -768,7 +756,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ScooterOrder?> finishRide({
|
Future<void> finishRide({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
required List<int> filesId,
|
required List<int> filesId,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -779,10 +767,6 @@ class ApiService {
|
|||||||
options: await _getAuthOptions(),
|
options: await _getAuthOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
||||||
return ScooterOrder.fromJson(response.data);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response?.statusCode == 400) {
|
if (e.response?.statusCode == 400) {
|
||||||
final data = e.response?.data;
|
final data = e.response?.data;
|
||||||
@@ -791,7 +775,9 @@ class ApiService {
|
|||||||
final firstError = data['message'][0]['message'].toString();
|
final firstError = data['message'][0]['message'].toString();
|
||||||
|
|
||||||
if (firstError.contains("Wrong start zone")) {
|
if (firstError.contains("Wrong start zone")) {
|
||||||
throw WrongZoneException(message: "Некорректная зона для завершения поездки.");
|
throw WrongZoneException(
|
||||||
|
message: "Некорректная зона для завершения поездки.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,7 +789,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ScooterOrder?> payRide(int orderId) async {
|
Future<void> payRide(int orderId) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.put(
|
final response = await _dio.put(
|
||||||
"$baseUrl/scooterorder/$orderId/pay",
|
"$baseUrl/scooterorder/$orderId/pay",
|
||||||
@@ -811,12 +797,12 @@ class ApiService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return ScooterOrder.fromJson(response.data);
|
// return ScooterOrder.fromJson(response.data);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
_handleDioError(e);
|
_handleDioError(e);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,7 +864,7 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ScooterOrder?> payScooterOrderWithPhotos({
|
Future<void> payScooterOrderWithPhotos({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
required int? cardId,
|
required int? cardId,
|
||||||
required bool isBalance,
|
required bool isBalance,
|
||||||
@@ -891,12 +877,10 @@ class ApiService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return ScooterOrder.fromJson(response.data);
|
return;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
_handleDioError(e);
|
_handleDioError(e);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,7 +902,6 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<List<Point>> getScooterOrderRouteHistory({required int id}) async {
|
Future<List<Point>> getScooterOrderRouteHistory({required int id}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
@@ -930,13 +913,19 @@ class ApiService {
|
|||||||
final String routeString = response.data['route'] ?? '[]';
|
final String routeString = response.data['route'] ?? '[]';
|
||||||
final List<dynamic> routeList = json.decode(routeString);
|
final List<dynamic> routeList = json.decode(routeString);
|
||||||
|
|
||||||
return routeList.map((item) => Point(
|
return routeList
|
||||||
|
.map(
|
||||||
|
(item) => Point(
|
||||||
(item[1] as num).toDouble(),
|
(item[1] as num).toDouble(),
|
||||||
(item[0] as num).toDouble(),
|
(item[0] as num).toDouble(),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw RouteHistoryNotFoundException(message: "История маршрута не найдена");
|
throw RouteHistoryNotFoundException(
|
||||||
|
message: "История маршрута не найдена",
|
||||||
|
);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response?.statusCode == 401) throw UnauthorizedException();
|
if (e.response?.statusCode == 401) throw UnauthorizedException();
|
||||||
if (e.response?.statusCode == 403) throw AuthBlockException();
|
if (e.response?.statusCode == 403) throw AuthBlockException();
|
||||||
@@ -1017,10 +1006,10 @@ class ApiService {
|
|||||||
if (event.data != null && event.data!.isNotEmpty) {
|
if (event.data != null && event.data!.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic> jsonData = jsonDecode(event.data!);
|
final Map<String, dynamic> jsonData = jsonDecode(event.data!);
|
||||||
print("✅ [SSE] Parsed JSON: $jsonData");
|
print("[SSE] Parsed JSON: $jsonData");
|
||||||
controller.add(jsonData);
|
controller.add(jsonData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("❌ [SSE] JSON Parse Error: $e | Raw: ${event.data}");
|
print("[SSE] JSON Parse Error: $e | Raw: ${event.data}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1038,6 +1027,42 @@ class ApiService {
|
|||||||
return controller.stream;
|
return controller.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getNotifications({
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
}) async {
|
||||||
|
final url = Uri.parse('$baseUrl/notification/client').replace(
|
||||||
|
queryParameters: {
|
||||||
|
'page': page.toString(),
|
||||||
|
'pageSize': pageSize.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final accessToken = await _securityService.getAccessToken();
|
||||||
|
if (accessToken == null) throw UnauthorizedException();
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": "Bearer $accessToken",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
||||||
|
if (data is Map<String, dynamic>) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
throw Exception('Expected Map with data and pagination');
|
||||||
|
} else if (response.statusCode == 401) {
|
||||||
|
throw UnauthorizedException();
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
throw AuthBlockException();
|
||||||
|
}
|
||||||
|
throw Exception('Ошибка сервера: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Certificate>> getCertificates() async {
|
Future<List<Certificate>> getCertificates() async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
@@ -1097,4 +1122,31 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> applyPromoCode(String code) async {
|
||||||
|
final token = await _securityService.getAccessToken();
|
||||||
|
if (token == null) throw Exception('No access token');
|
||||||
|
|
||||||
|
final response = await _dio.post(
|
||||||
|
'$baseUrl/promocode/apply',
|
||||||
|
data: {'code': code},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||||
|
return response.data;
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
throw PromoCodeNotFoundException();
|
||||||
|
} else {
|
||||||
|
throw Exception('API Error: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Добавь исключение (можно в отдельный файл)
|
||||||
|
class PromoCodeNotFoundException implements Exception {}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:be_happy/domain/entities/client_notification.dart';
|
import 'package:be_happy/domain/entities/client_notification.dart';
|
||||||
|
|
||||||
import '../../data/network/api_service.dart';
|
import '../../data/network/api_service.dart';
|
||||||
|
import '../../domain/entities/pagination_meta.dart';
|
||||||
import '../../domain/repositories/notification_repository.dart';
|
import '../../domain/repositories/notification_repository.dart';
|
||||||
|
import '../../domain/usecase/get_notifications_usecase.dart';
|
||||||
import '../models/client_notification_dto.dart';
|
import '../models/client_notification_dto.dart';
|
||||||
|
|
||||||
class NotificationRepositoryImpl implements NotificationRepository {
|
class NotificationRepositoryImpl implements NotificationRepository {
|
||||||
@@ -33,4 +35,30 @@ class NotificationRepositoryImpl implements NotificationRepository {
|
|||||||
void closeStream() {
|
void closeStream() {
|
||||||
// соединение закрывается автоматически при отписке от stream
|
// соединение закрывается автоматически при отписке от stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<NotificationsResult> getNotifications({
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _apiService.getNotifications(
|
||||||
|
page: page,
|
||||||
|
pageSize: pageSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
final notifications = (response['data'] as List)
|
||||||
|
.map((json) => ClientNotificationDto.fromJson(json).toEntity())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final pagination = PaginationMeta.fromJson(response['pagination']);
|
||||||
|
|
||||||
|
return NotificationsResult(
|
||||||
|
notifications: notifications,
|
||||||
|
pagination: pagination,
|
||||||
|
);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
throw Exception('Не удалось загрузить уведомления: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,26 +32,25 @@ class PaymentRepositoryImpl implements PaymentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<void>> addPaymentCard({
|
Future<Result<Map<String, dynamic>>> addPaymentCard(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
}) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final cardId = await apiService.addPaymentCard(
|
final result = await apiService.addPaymentCard(
|
||||||
cardNumber: cardNumber,
|
/*cardNumber: cardNumber,
|
||||||
cardHolder: cardHolder,
|
cardHolder: cardHolder,
|
||||||
expirationMonth: int.parse(expiryMonth),
|
expirationMonth: int.parse(expiryMonth),
|
||||||
expirationYear: int.parse(expiryYear),
|
expirationYear: int.parse(expiryYear),
|
||||||
cvv: cvv,
|
cvv: cvv,*/
|
||||||
);
|
);
|
||||||
|
|
||||||
// Сохраняем полный номер карты локально
|
// await securityService.saveCardFullNumber(cardId, cardNumber);
|
||||||
await securityService.saveCardFullNumber(cardId, cardNumber);
|
|
||||||
|
|
||||||
return Success(null);
|
return Success(result);
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
return Failure(AuthFailure(e.attemptsLeft));
|
return Failure(AuthFailure(e.attemptsLeft));
|
||||||
} on AuthBlockException catch (_) {
|
} on AuthBlockException catch (_) {
|
||||||
@@ -99,9 +98,9 @@ class PaymentRepositoryImpl implements PaymentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool>> activateSubscription(int optionId) async {
|
Future<Result<bool>> activateSubscription(int optionId, int cardId) async {
|
||||||
try {
|
try {
|
||||||
final success = await apiService.activateSubscription(optionId: optionId);
|
final success = await apiService.activateSubscription(optionId: optionId, cardId: cardId);
|
||||||
return Success(success);
|
return Success(success);
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
return Failure(AuthFailure(e.attemptsLeft));
|
return Failure(AuthFailure(e.attemptsLeft));
|
||||||
|
|||||||
23
lib/data/repositories/promo_code_repository_impl.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import '../../domain/entities/promo_code_result.dart';
|
||||||
|
import '../../domain/repositories/promo_code_repository.dart';
|
||||||
|
import '../models/promo_code_response_dto.dart';
|
||||||
|
import '../network/api_service.dart';
|
||||||
|
|
||||||
|
class PromoCodeRepositoryImpl implements PromoCodeRepository {
|
||||||
|
final ApiService apiService;
|
||||||
|
|
||||||
|
PromoCodeRepositoryImpl(this.apiService);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PromoCodeResult> applyPromoCode(String code) async {
|
||||||
|
try {
|
||||||
|
final data = await apiService.applyPromoCode(code);
|
||||||
|
final dto = PromoCodeResponseDto.fromJson(data);
|
||||||
|
return dto.toEntity();
|
||||||
|
} on PromoCodeNotFoundException {
|
||||||
|
throw Exception('Промокод не найден');
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Ошибка активации промокода: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import 'package:be_happy/domain/repositories/scooter_repository.dart';
|
|||||||
import '../../core/failures.dart';
|
import '../../core/failures.dart';
|
||||||
import '../../core/result.dart';
|
import '../../core/result.dart';
|
||||||
import '../../domain/entities/active_scooter_order.dart';
|
import '../../domain/entities/active_scooter_order.dart';
|
||||||
|
import '../../domain/entities/client_subscription.dart';
|
||||||
import '../../domain/entities/scooter.dart';
|
import '../../domain/entities/scooter.dart';
|
||||||
import '../../domain/entities/tariff.dart';
|
import '../../domain/entities/tariff.dart';
|
||||||
import '../../domain/entities/subscription.dart';
|
import '../../domain/entities/subscription.dart';
|
||||||
@@ -127,8 +128,8 @@ class ScooterRepositoryImpl extends ScooterRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<List<Subscription>>> getClientSubscriptions() async {
|
Future<Result<List<ClientSubscription>>> getClientSubscriptions() async {
|
||||||
late final Result<List<Subscription>> result;
|
late final Result<List<ClientSubscription>> result;
|
||||||
try {
|
try {
|
||||||
final subscriptions = await _apiService.getClientSubscriptions();
|
final subscriptions = await _apiService.getClientSubscriptions();
|
||||||
result = Success(subscriptions);
|
result = Success(subscriptions);
|
||||||
@@ -191,15 +192,11 @@ class ScooterRepositoryImpl extends ScooterRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<ScooterOrder>> cancelRide(int orderId) async {
|
Future<Result<void>> cancelRide(int orderId) async {
|
||||||
late final Result<ScooterOrder> result;
|
late final Result<void> result;
|
||||||
try {
|
try {
|
||||||
final order = await _apiService.cancelRide(orderId);
|
await _apiService.cancelRide(orderId);
|
||||||
if (order != null) {
|
result = Success(null);
|
||||||
result = Success(order);
|
|
||||||
} else {
|
|
||||||
result = Failure(UnknownFailure("Неизвестная ошибка"));
|
|
||||||
}
|
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
result = Failure(AuthFailure(e.attemptsLeft));
|
result = Failure(AuthFailure(e.attemptsLeft));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -245,18 +242,11 @@ class ScooterRepositoryImpl extends ScooterRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<ScooterOrder>> finishRide(int orderId, List<int> files) async {
|
Future<Result<void>> finishRide(int orderId, List<int> files) async {
|
||||||
late final Result<ScooterOrder> result;
|
late final Result<ScooterOrder> result;
|
||||||
try {
|
try {
|
||||||
final order = await _apiService.finishRide(
|
await _apiService.finishRide(orderId: orderId, filesId: files);
|
||||||
orderId: orderId,
|
result = Success(null);
|
||||||
filesId: files,
|
|
||||||
);
|
|
||||||
if (order != null) {
|
|
||||||
result = Success(order);
|
|
||||||
} else {
|
|
||||||
result = Failure(UnknownFailure("Неизвестная ошибка"));
|
|
||||||
}
|
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
result = Failure(AuthFailure(e.attemptsLeft));
|
result = Failure(AuthFailure(e.attemptsLeft));
|
||||||
} on WrongZoneException catch (e) {
|
} on WrongZoneException catch (e) {
|
||||||
@@ -268,15 +258,11 @@ class ScooterRepositoryImpl extends ScooterRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<ScooterOrder>> payRide(int orderId) async {
|
Future<Result<void>> payRide(int orderId) async {
|
||||||
late final Result<ScooterOrder> result;
|
late final Result<void> result;
|
||||||
try {
|
try {
|
||||||
final order = await _apiService.payRide(orderId);
|
await _apiService.payRide(orderId);
|
||||||
if (order != null) {
|
result = Success(null);
|
||||||
result = Success(order);
|
|
||||||
} else {
|
|
||||||
result = Failure(UnknownFailure("Неизвестная ошибка"));
|
|
||||||
}
|
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
result = Failure(AuthFailure(e.attemptsLeft));
|
result = Failure(AuthFailure(e.attemptsLeft));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -335,23 +321,19 @@ class ScooterRepositoryImpl extends ScooterRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<ScooterOrder>> payScooterOrderWithPhotos({
|
Future<Result<void>> payScooterOrderWithPhotos({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
required int? cardId,
|
required int? cardId,
|
||||||
required bool isBalance,
|
required bool isBalance,
|
||||||
}) async {
|
}) async {
|
||||||
late final Result<ScooterOrder> result;
|
late final Result<void> result;
|
||||||
try {
|
try {
|
||||||
final order = await _apiService.payScooterOrderWithPhotos(
|
final order = await _apiService.payScooterOrderWithPhotos(
|
||||||
orderId: orderId,
|
orderId: orderId,
|
||||||
cardId: cardId,
|
cardId: cardId,
|
||||||
isBalance: isBalance,
|
isBalance: isBalance,
|
||||||
);
|
);
|
||||||
if (order != null) {
|
result = Success(null);
|
||||||
result = Success(order);
|
|
||||||
} else {
|
|
||||||
result = Failure(UnknownFailure("Неизвестная ошибка"));
|
|
||||||
}
|
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
result = Failure(AuthFailure(e.attemptsLeft));
|
result = Failure(AuthFailure(e.attemptsLeft));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -85,13 +85,17 @@ import '../data/repositories/auth_repository_impl.dart';
|
|||||||
import '../data/repositories/news_repository_impl.dart';
|
import '../data/repositories/news_repository_impl.dart';
|
||||||
import '../data/repositories/pin_repository_impl.dart';
|
import '../data/repositories/pin_repository_impl.dart';
|
||||||
import '../data/repositories/profile_repository_impl.dart';
|
import '../data/repositories/profile_repository_impl.dart';
|
||||||
|
import '../data/repositories/promo_code_repository_impl.dart';
|
||||||
import '../data/service/news_api_service.dart';
|
import '../data/service/news_api_service.dart';
|
||||||
import '../domain/repositories/auth_repository.dart';
|
import '../domain/repositories/auth_repository.dart';
|
||||||
import '../domain/repositories/news_repository.dart';
|
import '../domain/repositories/news_repository.dart';
|
||||||
|
import '../domain/repositories/promo_code_repository.dart';
|
||||||
import '../domain/service/device_info_service.dart';
|
import '../domain/service/device_info_service.dart';
|
||||||
import '../domain/usecase/activate_subscription_usecase.dart';
|
import '../domain/usecase/activate_subscription_usecase.dart';
|
||||||
|
import '../domain/usecase/apply_promo_code_usecase.dart';
|
||||||
import '../domain/usecase/get_client_subscriptions_usecase.dart';
|
import '../domain/usecase/get_client_subscriptions_usecase.dart';
|
||||||
import '../domain/usecase/get_news_by_id_usecase.dart';
|
import '../domain/usecase/get_news_by_id_usecase.dart';
|
||||||
|
import '../domain/usecase/get_notifications_usecase.dart';
|
||||||
import '../domain/usecase/get_scooter_by_title_usecase.dart';
|
import '../domain/usecase/get_scooter_by_title_usecase.dart';
|
||||||
import '../domain/usecase/get_scooter_order_history_usecase.dart';
|
import '../domain/usecase/get_scooter_order_history_usecase.dart';
|
||||||
import '../domain/usecase/remove_payment_card_usecase.dart';
|
import '../domain/usecase/remove_payment_card_usecase.dart';
|
||||||
@@ -100,7 +104,9 @@ import '../presentation/viewmodel/auth_bloc.dart';
|
|||||||
import '../presentation/viewmodel/edit_profile_bloc.dart';
|
import '../presentation/viewmodel/edit_profile_bloc.dart';
|
||||||
import '../presentation/viewmodel/map_bloc.dart';
|
import '../presentation/viewmodel/map_bloc.dart';
|
||||||
import '../presentation/viewmodel/news_bloc.dart';
|
import '../presentation/viewmodel/news_bloc.dart';
|
||||||
|
import '../presentation/viewmodel/notifications_bloc.dart';
|
||||||
import '../presentation/viewmodel/order_history_bloc.dart';
|
import '../presentation/viewmodel/order_history_bloc.dart';
|
||||||
|
import '../presentation/viewmodel/promo_code_bloc.dart';
|
||||||
import '../presentation/viewmodel/scooter_detail_modal_bloc.dart';
|
import '../presentation/viewmodel/scooter_detail_modal_bloc.dart';
|
||||||
import '../presentation/viewmodel/subscription_list_bloc.dart';
|
import '../presentation/viewmodel/subscription_list_bloc.dart';
|
||||||
import '../presentation/viewmodel/verify_code_bloc.dart';
|
import '../presentation/viewmodel/verify_code_bloc.dart';
|
||||||
@@ -110,7 +116,7 @@ final getIt = GetIt.instance;
|
|||||||
Future<void> setupDependencies() async {
|
Future<void> setupDependencies() async {
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
final dio = Dio();
|
final dio = Dio();
|
||||||
dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true));
|
dio.interceptors.add(LogInterceptor(/*requestHeader: false, responseHeader:false, */responseBody: true, requestBody: true));
|
||||||
dio.interceptors.add(AuthInterceptor());
|
dio.interceptors.add(AuthInterceptor());
|
||||||
// HTTP
|
// HTTP
|
||||||
getIt.registerSingleton<http.Client>(http.Client());
|
getIt.registerSingleton<http.Client>(http.Client());
|
||||||
@@ -286,15 +292,22 @@ Future<void> setupDependencies() async {
|
|||||||
getIt.registerSingleton<GetScooterByTitleUsecase>(
|
getIt.registerSingleton<GetScooterByTitleUsecase>(
|
||||||
GetScooterByTitleUsecase(getIt()),
|
GetScooterByTitleUsecase(getIt()),
|
||||||
);
|
);
|
||||||
|
getIt.registerSingleton<GetNotificationsUsecase>(
|
||||||
|
GetNotificationsUsecase(getIt<NotificationRepository>()),
|
||||||
|
);
|
||||||
|
getIt.registerFactory<NotificationsBloc>(
|
||||||
|
() => NotificationsBloc(getIt<GetNotificationsUsecase>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
// Blocs
|
// Blocs
|
||||||
getIt.registerLazySingleton<SplashBloc>(() => SplashBloc(getIt()));
|
getIt.registerLazySingleton<SplashBloc>(() => SplashBloc(getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<PhoneAuthBloc>(() => PhoneAuthBloc(getIt()));
|
getIt.registerFactory<PhoneAuthBloc>(() => PhoneAuthBloc(getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<VerifyCodeBloc>(() => VerifyCodeBloc(getIt()));
|
getIt.registerFactory<VerifyCodeBloc>(() => VerifyCodeBloc(getIt(), getIt(), getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<ProfileBloc>(() => ProfileBloc(getIt(), getIt(), getIt()));
|
getIt.registerLazySingleton<ProfileBloc>(() => ProfileBloc(getIt(), getIt(), getIt()));
|
||||||
|
|
||||||
getIt.registerFactory<EditProfileBloc>(
|
getIt.registerFactory<EditProfileBloc>(
|
||||||
() => EditProfileBloc(getIt(), getIt()),
|
() => EditProfileBloc(getIt(), getIt()),
|
||||||
@@ -310,6 +323,7 @@ Future<void> setupDependencies() async {
|
|||||||
getIt(),
|
getIt(),
|
||||||
getIt(),
|
getIt(),
|
||||||
getIt(),
|
getIt(),
|
||||||
|
getIt(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -323,7 +337,7 @@ Future<void> setupDependencies() async {
|
|||||||
|
|
||||||
|
|
||||||
getIt.registerFactory<PaymentMethodSheetBloc>(
|
getIt.registerFactory<PaymentMethodSheetBloc>(
|
||||||
() => PaymentMethodSheetBloc(getIt()),
|
() => PaymentMethodSheetBloc(getIt(), getIt(), getIt(), getIt()),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactory<CurrentRidesBloc>(() => CurrentRidesBloc(getIt()));
|
getIt.registerFactory<CurrentRidesBloc>(() => CurrentRidesBloc(getIt()));
|
||||||
@@ -373,4 +387,20 @@ Future<void> setupDependencies() async {
|
|||||||
getIt.registerFactory<ScooterCodeBloc>(
|
getIt.registerFactory<ScooterCodeBloc>(
|
||||||
() => ScooterCodeBloc(getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>()),
|
() => ScooterCodeBloc(getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Repository
|
||||||
|
getIt.registerSingleton<PromoCodeRepository>(
|
||||||
|
PromoCodeRepositoryImpl(getIt<ApiService>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// UseCase
|
||||||
|
getIt.registerSingleton<ApplyPromoCodeUsecase>(
|
||||||
|
ApplyPromoCodeUsecase(getIt<PromoCodeRepository>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bloc (factory, т.к. экран создаёт новый экземпляр)
|
||||||
|
getIt.registerFactory<PromoCodeBloc>(
|
||||||
|
() => PromoCodeBloc(getIt<ApplyPromoCodeUsecase>()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
24
lib/domain/entities/client_subscription.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:be_happy/domain/entities/subscription.dart';
|
||||||
|
|
||||||
|
class ClientSubscription {
|
||||||
|
final int id;
|
||||||
|
final int subscriptionId;
|
||||||
|
final Subscription subscription;
|
||||||
|
final DateTime? expiredAt;
|
||||||
|
|
||||||
|
ClientSubscription({
|
||||||
|
required this.id,
|
||||||
|
required this.subscriptionId,
|
||||||
|
required this.subscription,
|
||||||
|
this.expiredAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ClientSubscription.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ClientSubscription(
|
||||||
|
id: json['id'] ?? 0,
|
||||||
|
subscriptionId: json['subscriptionId'] ?? 0,
|
||||||
|
subscription: Subscription.fromJson(json['subscription'] as Map<String, dynamic>),
|
||||||
|
expiredAt: json['expiredAt'] != null ? DateTime.parse(json['expiredAt']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lib/domain/entities/pagination_meta.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
class PaginationMeta {
|
||||||
|
final int total;
|
||||||
|
final int lastPage;
|
||||||
|
final int currentPage;
|
||||||
|
final int totalPerPage;
|
||||||
|
final int? prevPage;
|
||||||
|
final int? nextPage;
|
||||||
|
|
||||||
|
PaginationMeta({
|
||||||
|
required this.total,
|
||||||
|
required this.lastPage,
|
||||||
|
required this.currentPage,
|
||||||
|
required this.totalPerPage,
|
||||||
|
this.prevPage,
|
||||||
|
this.nextPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PaginationMeta.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PaginationMeta(
|
||||||
|
total: json['total'] as int,
|
||||||
|
lastPage: json['lastPage'] as int,
|
||||||
|
currentPage: json['currentPage'] as int,
|
||||||
|
totalPerPage: json['totalPerPage'] as int,
|
||||||
|
prevPage: json['prevPage'] as int?,
|
||||||
|
nextPage: json['nextPage'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,27 @@
|
|||||||
class PaymentCard {
|
class PaymentCard {
|
||||||
final int id;
|
final int id;
|
||||||
final int clientId;
|
final String? bindingId;
|
||||||
final int expirationMonth;
|
|
||||||
final int expirationYear;
|
|
||||||
final String cardHolder;
|
|
||||||
final String cardLastNumber;
|
final String cardLastNumber;
|
||||||
final bool isMain;
|
final bool isMain;
|
||||||
final String type;
|
|
||||||
|
|
||||||
PaymentCard({
|
PaymentCard({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.clientId,
|
required this.bindingId,
|
||||||
required this.expirationMonth,
|
|
||||||
required this.expirationYear,
|
|
||||||
required this.cardHolder,
|
|
||||||
required this.cardLastNumber,
|
required this.cardLastNumber,
|
||||||
required this.isMain,
|
required this.isMain,
|
||||||
required this.type,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PaymentCard copyWith({
|
PaymentCard copyWith({
|
||||||
int? id,
|
int? id,
|
||||||
int? clientId,
|
String? bindingId,
|
||||||
int? expirationMonth,
|
|
||||||
int? expirationYear,
|
|
||||||
String? cardHolder,
|
|
||||||
String? cardLastNumber,
|
String? cardLastNumber,
|
||||||
bool? isMain,
|
bool? isMain,
|
||||||
String? type,
|
|
||||||
}) {
|
}) {
|
||||||
return PaymentCard(
|
return PaymentCard(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
clientId: clientId ?? this.clientId,
|
bindingId: bindingId ?? this.bindingId,
|
||||||
expirationMonth: expirationMonth ?? this.expirationMonth,
|
|
||||||
expirationYear: expirationYear ?? this.expirationYear,
|
|
||||||
cardHolder: cardHolder ?? this.cardHolder,
|
|
||||||
cardLastNumber: cardLastNumber ?? this.cardLastNumber,
|
cardLastNumber: cardLastNumber ?? this.cardLastNumber,
|
||||||
isMain: isMain ?? this.isMain,
|
isMain: isMain ?? this.isMain,
|
||||||
type: type ?? this.type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
lib/domain/entities/promo_code_result.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class PromoCodeResult {
|
||||||
|
final bool success;
|
||||||
|
final double balance;
|
||||||
|
|
||||||
|
PromoCodeResult({
|
||||||
|
required this.success,
|
||||||
|
required this.balance,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -43,8 +43,9 @@ class Scooter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Scooter{id: $id, title: $title}';
|
return 'Scooter{id: $id, title: $title, status: $status, latitude: $latitude, longitude: $longitude, batteryLevel: $batteryLevel, isOnline: $isOnline}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'scooter.dart';
|
|||||||
class ScooterOrder {
|
class ScooterOrder {
|
||||||
final int id;
|
final int id;
|
||||||
final int scooterId;
|
final int scooterId;
|
||||||
final Scooter? scooter;
|
final Scooter scooter;
|
||||||
final int? planId;
|
final int? planId;
|
||||||
final ScooterPlan? plan;
|
final ScooterPlan? plan;
|
||||||
final int clientId;
|
final int clientId;
|
||||||
@@ -29,11 +29,14 @@ class ScooterOrder {
|
|||||||
final String? cancelDescription;
|
final String? cancelDescription;
|
||||||
final double mileage; //эту
|
final double mileage; //эту
|
||||||
final double avgSpeed; //эту и снизу еще 4 есть
|
final double avgSpeed; //эту и снизу еще 4 есть
|
||||||
|
final String payTotalPricePrint;
|
||||||
|
final String unpayTotalPricePrint;
|
||||||
|
final String totalOrderPricePrint;
|
||||||
|
|
||||||
ScooterOrder({
|
ScooterOrder({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.scooterId,
|
required this.scooterId,
|
||||||
this.scooter,
|
required this.scooter,
|
||||||
this.planId,
|
this.planId,
|
||||||
this.plan,
|
this.plan,
|
||||||
required this.clientId,
|
required this.clientId,
|
||||||
@@ -57,15 +60,20 @@ class ScooterOrder {
|
|||||||
this.expiresAt,
|
this.expiresAt,
|
||||||
this.cancelAt,
|
this.cancelAt,
|
||||||
this.cancelDescription,
|
this.cancelDescription,
|
||||||
required this.mileage, //эту
|
required this.mileage,
|
||||||
required this.avgSpeed, //эту и снизу еще 2 есть
|
required this.avgSpeed,
|
||||||
|
required this.payTotalPricePrint, // оплачено
|
||||||
|
required this.unpayTotalPricePrint, //неоплачено
|
||||||
|
required this.totalOrderPricePrint, //общая
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ScooterOrder.fromJson(Map<String, dynamic> json) {
|
factory ScooterOrder.fromJson(Map<String, dynamic> json) {
|
||||||
|
final paymentInfo = json['paymentInfo'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
return ScooterOrder(
|
return ScooterOrder(
|
||||||
id: json['id'] ?? 0,
|
id: json['id'] ?? 0,
|
||||||
scooterId: json['scooterId'] ?? 0,
|
scooterId: json['scooterId'] ?? 0,
|
||||||
scooter: json['scooter'] != null ? Scooter.fromJson(json['scooter']) : null,
|
scooter: Scooter.fromJson(json['scooter']),
|
||||||
planId: json['planId'],
|
planId: json['planId'],
|
||||||
plan: json['plan'] != null ? ScooterPlan.fromJson(json['plan']) : null,
|
plan: json['plan'] != null ? ScooterPlan.fromJson(json['plan']) : null,
|
||||||
clientId: json['clientId'] ?? 0,
|
clientId: json['clientId'] ?? 0,
|
||||||
@@ -101,8 +109,11 @@ class ScooterOrder {
|
|||||||
? DateTime.parse(json['cancelAt'])
|
? DateTime.parse(json['cancelAt'])
|
||||||
: null,
|
: null,
|
||||||
cancelDescription: json['cancelDescription'],
|
cancelDescription: json['cancelDescription'],
|
||||||
mileage: (json['mileage'] as num?)?.toDouble() ?? 0.0, //эту
|
mileage: (json['mileage'] as num?)?.toDouble() ?? 0.0,
|
||||||
avgSpeed: (json['avgSpeed'] as num?)?.toDouble() ?? 0.0, //эту
|
avgSpeed: (json['avgSpeed'] as num?)?.toDouble() ?? 0.0,
|
||||||
|
payTotalPricePrint: paymentInfo?['payTotalPricePrint'] ?? '',
|
||||||
|
unpayTotalPricePrint: paymentInfo?['unpayTotalPricePrint'] ?? '',
|
||||||
|
totalOrderPricePrint: paymentInfo?['totalOrderPricePrint'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ class Subscription {
|
|||||||
final String title;
|
final String title;
|
||||||
final String shortDescription;
|
final String shortDescription;
|
||||||
final String fullDescription;
|
final String fullDescription;
|
||||||
|
final String fullDescriptionJson;
|
||||||
final int planId;
|
final int planId;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
final bool isCurrent;
|
||||||
final String currency;
|
final String currency;
|
||||||
final DateTime? activeFrom;
|
final DateTime? activeFrom;
|
||||||
final DateTime? activeTo;
|
final DateTime? activeTo;
|
||||||
@@ -21,6 +23,7 @@ class Subscription {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.shortDescription,
|
required this.shortDescription,
|
||||||
required this.fullDescription,
|
required this.fullDescription,
|
||||||
|
required this.fullDescriptionJson,
|
||||||
required this.planId,
|
required this.planId,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
@@ -28,6 +31,7 @@ class Subscription {
|
|||||||
this.activeTo,
|
this.activeTo,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
|
required this.isCurrent,
|
||||||
required this.options,
|
required this.options,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ class Subscription {
|
|||||||
title: json['title'] ?? '',
|
title: json['title'] ?? '',
|
||||||
shortDescription: json['shortDescription'] ?? '',
|
shortDescription: json['shortDescription'] ?? '',
|
||||||
fullDescription: json['fullDescription'] ?? '',
|
fullDescription: json['fullDescription'] ?? '',
|
||||||
|
fullDescriptionJson: json['fullDescriptionJson'] ?? '',
|
||||||
planId: json['planId'] ?? 0,
|
planId: json['planId'] ?? 0,
|
||||||
isActive: json['isActive'] ?? false,
|
isActive: json['isActive'] ?? false,
|
||||||
currency: currencyData['currency'] ?? 'BYN',
|
currency: currencyData['currency'] ?? 'BYN',
|
||||||
@@ -48,6 +53,7 @@ class Subscription {
|
|||||||
activeTo: json['activeTo'] != null ? DateTime.parse(json['activeTo']) : null,
|
activeTo: json['activeTo'] != null ? DateTime.parse(json['activeTo']) : null,
|
||||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : DateTime.now(),
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : DateTime.now(),
|
||||||
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : DateTime.now(),
|
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : DateTime.now(),
|
||||||
|
isCurrent: json['isCurrent'] ?? false,
|
||||||
options: optionsData.map((e) => SubscriptionPeriod.fromJson(e as Map<String, dynamic>)).toList(),
|
options: optionsData.map((e) => SubscriptionPeriod.fromJson(e as Map<String, dynamic>)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class UserProfile {
|
|||||||
String birthDate;
|
String birthDate;
|
||||||
String phone;
|
String phone;
|
||||||
String email;
|
String email;
|
||||||
int? balance;
|
double? balance;
|
||||||
int? avatarId;
|
int? avatarId;
|
||||||
String? avatarUrl;
|
String? avatarUrl;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class UserProfile {
|
|||||||
String? name,
|
String? name,
|
||||||
String? birthDate,
|
String? birthDate,
|
||||||
String? email,
|
String? email,
|
||||||
int? balance,
|
double? balance,
|
||||||
int? avatarId,
|
int? avatarId,
|
||||||
String? avatarUrl,
|
String? avatarUrl,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:be_happy/domain/entities/point.dart';
|
import 'package:be_happy/domain/entities/point.dart';
|
||||||
|
|
||||||
class Zone {
|
class Zone {
|
||||||
@@ -9,7 +8,10 @@ class Zone {
|
|||||||
final String type;
|
final String type;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final String shapeType;
|
final String shapeType;
|
||||||
final List<Point> points;
|
// Трехмерная структура для поддержки MultiPolygon и дыр (holes):
|
||||||
|
// geometry[индекс_полигона][индекс_кольца][индекс_точки]
|
||||||
|
// индекс_кольца == 0 — внешняя граница, > 0 — внутренние вырезы (дыры)
|
||||||
|
final List<List<List<Point>>> geometry;
|
||||||
final String speedLimit;
|
final String speedLimit;
|
||||||
|
|
||||||
Zone({
|
Zone({
|
||||||
@@ -19,38 +21,84 @@ class Zone {
|
|||||||
required this.type,
|
required this.type,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
required this.shapeType,
|
required this.shapeType,
|
||||||
required this.points,
|
required this.geometry,
|
||||||
required this.speedLimit,
|
required this.speedLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Геттер для обратной совместимости (возвращает внешнее кольцо первого полигона)
|
||||||
|
List<Point> get points {
|
||||||
|
if (geometry.isNotEmpty && geometry[0].isNotEmpty) {
|
||||||
|
return geometry[0][0];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
factory Zone.fromJson(Map<String, dynamic> json) {
|
factory Zone.fromJson(Map<String, dynamic> json) {
|
||||||
final zoneCoordinates = json['coordinates'] as Map<String, dynamic>? ?? {};
|
final zoneCoordinates = json['coordinates'] as Map<String, dynamic>? ?? {};
|
||||||
final String coordsString = zoneCoordinates['coordinates'] ?? '[]';
|
final String coordsString = zoneCoordinates['coordinates'] ?? '[]';
|
||||||
final String shapeType = zoneCoordinates['type'] ?? 'Polygon';
|
final String shapeType = zoneCoordinates['type'] ?? 'Polygon';
|
||||||
|
|
||||||
List<Point> points = [];
|
List<List<List<Point>>> parsedGeometry = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final dynamic decoded = jsonDecode(coordsString);
|
final dynamic decoded = jsonDecode(coordsString);
|
||||||
|
|
||||||
if (decoded is List && decoded.isNotEmpty) {
|
if (decoded is List && decoded.isNotEmpty) {
|
||||||
List<dynamic> targetList = [];
|
|
||||||
|
|
||||||
if (shapeType == 'Polygon') {
|
if (shapeType == 'Polygon') {
|
||||||
// У полигона структура [[[lat, lon], ...]] -> уходим на 1 уровень вглубь
|
// Структура: [[[lon, lat], [lon, lat], ...], [hole1], ...]
|
||||||
targetList = decoded[0] as List<dynamic>;
|
List<List<Point>> polygonRings = [];
|
||||||
} else {
|
for (var ring in decoded) {
|
||||||
// У LineString структура [[lat, lon], ...] -> используем как есть
|
if (ring is List) {
|
||||||
targetList = decoded;
|
List<Point> ringPoints = [];
|
||||||
|
for (var coord in ring) {
|
||||||
|
if (coord is List && coord.length >= 2) {
|
||||||
|
// В GeoJSON сначала идет Longitude, затем Latitude
|
||||||
|
ringPoints.add(Point(
|
||||||
|
(coord[1] as num).toDouble(),
|
||||||
|
(coord[0] as num).toDouble(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (ringPoints.isNotEmpty) polygonRings.add(ringPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
|
||||||
|
|
||||||
points = targetList.map((item) {
|
} else if (shapeType == 'MultiPolygon') {
|
||||||
final List<dynamic> coords = item as List<dynamic>;
|
// Структура: [[[[lon, lat], ...]], [[[lon, lat], ...]]]
|
||||||
return Point(
|
for (var poly in decoded) {
|
||||||
(coords[1] as num).toDouble(),
|
if (poly is List) {
|
||||||
(coords[0] as num).toDouble(),
|
List<List<Point>> polygonRings = [];
|
||||||
);
|
for (var ring in poly) {
|
||||||
}).toList();
|
if (ring is List) {
|
||||||
|
List<Point> ringPoints = [];
|
||||||
|
for (var coord in ring) {
|
||||||
|
if (coord is List && coord.length >= 2) {
|
||||||
|
ringPoints.add(Point(
|
||||||
|
(coord[1] as num).toDouble(),
|
||||||
|
(coord[0] as num).toDouble(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ringPoints.isNotEmpty) polygonRings.add(ringPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (polygonRings.isNotEmpty) parsedGeometry.add(polygonRings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback для LineString или плоских списков
|
||||||
|
List<Point> ringPoints = [];
|
||||||
|
for (var coord in decoded) {
|
||||||
|
if (coord is List && coord.length >= 2) {
|
||||||
|
ringPoints.add(Point(
|
||||||
|
(coord[1] as num).toDouble(),
|
||||||
|
(coord[0] as num).toDouble(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ringPoints.isNotEmpty) parsedGeometry.add([ringPoints]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("PARSE ERROR for Zone ID ${json['id']}: $e");
|
print("PARSE ERROR for Zone ID ${json['id']}: $e");
|
||||||
@@ -64,11 +112,12 @@ class Zone {
|
|||||||
isActive: json['isActive'] ?? false,
|
isActive: json['isActive'] ?? false,
|
||||||
speedLimit: json['speedLimit'] ?? '',
|
speedLimit: json['speedLimit'] ?? '',
|
||||||
shapeType: shapeType,
|
shapeType: shapeType,
|
||||||
points: points,
|
geometry: parsedGeometry,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Zone{id: $id, title: $title, type: $type, points: $points}';
|
return 'Zone{id: $id, title: $title, type: $type, polygonsCount: ${geometry.length}}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:be_happy/domain/entities/client_notification.dart';
|
import 'package:be_happy/domain/entities/client_notification.dart';
|
||||||
|
|
||||||
|
import '../usecase/get_notifications_usecase.dart';
|
||||||
|
|
||||||
abstract class NotificationRepository {
|
abstract class NotificationRepository {
|
||||||
/// Устанавливает постоянное SSE-соединение и возвращает поток уведомлений
|
/// Устанавливает постоянное SSE-соединение и возвращает поток уведомлений
|
||||||
Stream<ClientNotification> getNotificationsStream();
|
Stream<ClientNotification> getNotificationsStream();
|
||||||
@@ -9,4 +11,9 @@ abstract class NotificationRepository {
|
|||||||
|
|
||||||
/// Закрывает SSE-соединение
|
/// Закрывает SSE-соединение
|
||||||
void closeStream();
|
void closeStream();
|
||||||
}
|
|
||||||
|
/// получить список уведомлений
|
||||||
|
Future<NotificationsResult> getNotifications({
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
});}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ import '../entities/payment_card.dart';
|
|||||||
abstract class PaymentRepository {
|
abstract class PaymentRepository {
|
||||||
Future<Result<List<PaymentCard>>> getPaymentCards();
|
Future<Result<List<PaymentCard>>> getPaymentCards();
|
||||||
|
|
||||||
Future<Result<void>> addPaymentCard({
|
Future<Result<Map<String, dynamic>>> addPaymentCard(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
});
|
);
|
||||||
|
|
||||||
Future<Result<void>> setMainPaymentCard(int cardId);
|
Future<Result<void>> setMainPaymentCard(int cardId);
|
||||||
|
|
||||||
Future<Result<void>> removePaymentCard(int cardId);
|
Future<Result<void>> removePaymentCard(int cardId);
|
||||||
|
|
||||||
Future<Result<bool>> activateSubscription(int optionId);
|
Future<Result<bool>> activateSubscription(int optionId, int cardId);
|
||||||
}
|
}
|
||||||
5
lib/domain/repositories/promo_code_repository.dart
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import '../entities/promo_code_result.dart';
|
||||||
|
|
||||||
|
abstract class PromoCodeRepository {
|
||||||
|
Future<PromoCodeResult> applyPromoCode(String code);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:be_happy/domain/entities/active_scooter_order.dart';
|
import 'package:be_happy/domain/entities/active_scooter_order.dart';
|
||||||
|
|
||||||
import '../../core/result.dart';
|
import '../../core/result.dart';
|
||||||
|
import '../entities/client_subscription.dart';
|
||||||
import '../entities/point.dart';
|
import '../entities/point.dart';
|
||||||
import '../entities/scooter.dart';
|
import '../entities/scooter.dart';
|
||||||
import '../entities/subscription.dart';
|
import '../entities/subscription.dart';
|
||||||
@@ -16,7 +17,7 @@ abstract class ScooterRepository {
|
|||||||
Future<Result<List<Tariff>>> getAvailableTariffs(int scooterId);
|
Future<Result<List<Tariff>>> getAvailableTariffs(int scooterId);
|
||||||
Future<Result<List<Subscription>>> getAvailableSubscriptions();
|
Future<Result<List<Subscription>>> getAvailableSubscriptions();
|
||||||
Future<Result<Subscription>> getSubscriptionById(int id);
|
Future<Result<Subscription>> getSubscriptionById(int id);
|
||||||
Future<Result<List<Subscription>>> getClientSubscriptions();
|
Future<Result<List<ClientSubscription>>> getClientSubscriptions();
|
||||||
Future<Result<ScooterOrder>> bookScooter({
|
Future<Result<ScooterOrder>> bookScooter({
|
||||||
required int scooterId,
|
required int scooterId,
|
||||||
required int planId,
|
required int planId,
|
||||||
@@ -26,17 +27,17 @@ abstract class ScooterRepository {
|
|||||||
required bool isInsurance,
|
required bool isInsurance,
|
||||||
});
|
});
|
||||||
Future<Result<ScooterOrder>> startRide(int orderId);
|
Future<Result<ScooterOrder>> startRide(int orderId);
|
||||||
Future<Result<ScooterOrder>> cancelRide(int orderId);
|
Future<Result<void>> cancelRide(int orderId);
|
||||||
Future<Result<ScooterOrder>> pauseRide(int orderId);
|
Future<Result<ScooterOrder>> pauseRide(int orderId);
|
||||||
Future<Result<ScooterOrder>> resumeRide(int orderId);
|
Future<Result<ScooterOrder>> resumeRide(int orderId);
|
||||||
Future<Result<ScooterOrder>> finishRide(int orderId, List<int> files);
|
Future<Result<void>> finishRide(int orderId, List<int> files);
|
||||||
Future<Result<ScooterOrder>> payRide(int orderId);
|
Future<Result<void>> payRide(int orderId);
|
||||||
Future<Result<List<ScooterOrder>>> getClientOrders();
|
Future<Result<List<ScooterOrder>>> getClientOrders();
|
||||||
Future<Result<List<int>>> uploadScooterPhotos(List<File> images);
|
Future<Result<List<int>>> uploadScooterPhotos(List<File> images);
|
||||||
Future<Result<ActiveScooterOrder>> updateScooterOrderData({
|
Future<Result<ActiveScooterOrder>> updateScooterOrderData({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
});
|
});
|
||||||
Future<Result<ScooterOrder>> payScooterOrderWithPhotos({
|
Future<Result<void>> payScooterOrderWithPhotos({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
required int? cardId,
|
required int? cardId,
|
||||||
required bool isBalance,
|
required bool isBalance,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class ActivateSubscriptionUsecase {
|
|||||||
|
|
||||||
ActivateSubscriptionUsecase(this.repository);
|
ActivateSubscriptionUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<bool>> call(int optionId) {
|
Future<Result<bool>> call(int optionId, int cardId) {
|
||||||
return repository.activateSubscription(optionId);
|
return repository.activateSubscription(optionId, cardId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ class AddPaymentCardUsecase {
|
|||||||
|
|
||||||
AddPaymentCardUsecase(this.repository);
|
AddPaymentCardUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<void>> call({
|
Future<Result<Map<String, dynamic>>> call(
|
||||||
required String cardNumber,
|
/*required String cardNumber,
|
||||||
required String cardHolder,
|
required String cardHolder,
|
||||||
required String expiryMonth,
|
required String expiryMonth,
|
||||||
required String expiryYear,
|
required String expiryYear,
|
||||||
required String cvv,
|
required String cvv,*/
|
||||||
}) {
|
) {
|
||||||
return repository.addPaymentCard(
|
return repository.addPaymentCard(
|
||||||
cardNumber: cardNumber,
|
/*cardNumber: cardNumber,
|
||||||
cardHolder: cardHolder,
|
cardHolder: cardHolder,
|
||||||
expiryMonth: expiryMonth,
|
expiryMonth: expiryMonth,
|
||||||
expiryYear: expiryYear,
|
expiryYear: expiryYear,
|
||||||
cvv: cvv,
|
cvv: cvv,*/
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
lib/domain/usecase/apply_promo_code_usecase.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import '../entities/promo_code_result.dart';
|
||||||
|
import '../repositories/promo_code_repository.dart';
|
||||||
|
|
||||||
|
class ApplyPromoCodeUsecase {
|
||||||
|
final PromoCodeRepository repository;
|
||||||
|
|
||||||
|
ApplyPromoCodeUsecase(this.repository);
|
||||||
|
|
||||||
|
Future<PromoCodeResult> call(String code) {
|
||||||
|
return repository.applyPromoCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ class CancelRideUsecase {
|
|||||||
|
|
||||||
CancelRideUsecase(this.repository);
|
CancelRideUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<ScooterOrder>> call(int orderId) {
|
Future<Result<void>> call(int orderId) {
|
||||||
return repository.cancelRide(orderId);
|
return repository.cancelRide(orderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class FinishRideUsecase {
|
|||||||
|
|
||||||
FinishRideUsecase(this.repository);
|
FinishRideUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<ScooterOrder>> call(int orderId, List<int> files) {
|
Future<Result<void>> call(int orderId, List<int> files) {
|
||||||
return repository.finishRide(orderId, files);
|
return repository.finishRide(orderId, files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:be_happy/core/result.dart';
|
import 'package:be_happy/core/result.dart';
|
||||||
import 'package:be_happy/domain/entities/scooter_order.dart';
|
import 'package:be_happy/domain/entities/scooter_order.dart';
|
||||||
|
|
||||||
|
import '../entities/client_subscription.dart';
|
||||||
import '../repositories/scooter_repository.dart';
|
import '../repositories/scooter_repository.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ class GetClientSubscriptionsUsecase {
|
|||||||
|
|
||||||
GetClientSubscriptionsUsecase(this.repository);
|
GetClientSubscriptionsUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<List<Subscription>>> call() {
|
Future<Result<List<ClientSubscription>>> call() {
|
||||||
return repository.getClientSubscriptions();
|
return repository.getClientSubscriptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/domain/usecase/get_notifications_usecase.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import '../entities/client_notification.dart';
|
||||||
|
import '../entities/pagination_meta.dart';
|
||||||
|
import '../repositories/notification_repository.dart';
|
||||||
|
|
||||||
|
class GetNotificationsUsecase {
|
||||||
|
final NotificationRepository repository;
|
||||||
|
|
||||||
|
GetNotificationsUsecase(this.repository);
|
||||||
|
|
||||||
|
Future<NotificationsResult> call({int page = 1, int pageSize = 10}) {
|
||||||
|
return repository.getNotifications(page: page, pageSize: pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationsResult {
|
||||||
|
final List<ClientNotification> notifications;
|
||||||
|
final PaginationMeta pagination;
|
||||||
|
|
||||||
|
NotificationsResult({
|
||||||
|
required this.notifications,
|
||||||
|
required this.pagination,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ class PayRideUsecase {
|
|||||||
|
|
||||||
PayRideUsecase(this.repository);
|
PayRideUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<ScooterOrder>> call(int orderId, int? cardId,
|
Future<Result<void>> call(int orderId, int? cardId,
|
||||||
bool isBalance) {
|
bool isBalance) {
|
||||||
return repository.payScooterOrderWithPhotos(orderId: orderId, cardId: cardId, isBalance: isBalance);
|
return repository.payScooterOrderWithPhotos(orderId: orderId, cardId: cardId, isBalance: isBalance);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class PayScooterOrderWithPhotosUsecase {
|
|||||||
|
|
||||||
PayScooterOrderWithPhotosUsecase(this.repository);
|
PayScooterOrderWithPhotosUsecase(this.repository);
|
||||||
|
|
||||||
Future<Result<ScooterOrder>> call({
|
Future<Result<void>> call({
|
||||||
required int orderId,
|
required int orderId,
|
||||||
required int cardId,
|
required int cardId,
|
||||||
required bool isBalance,
|
required bool isBalance,
|
||||||
|
|||||||
@@ -23,9 +23,21 @@ import 'di/service_locator.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
SystemChrome.setEnabledSystemUIMode(
|
||||||
|
SystemUiMode.manual,
|
||||||
|
overlays: [SystemUiOverlay.top],
|
||||||
|
);
|
||||||
|
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await setupDependencies();
|
await setupDependencies();
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +57,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider(create: (context) => getIt<VerifyCodeBloc>()),
|
BlocProvider(create: (context) => getIt<VerifyCodeBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<PinBloc>()),
|
BlocProvider(create: (context) => getIt<PinBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<MapBloc>()),
|
BlocProvider(create: (context) => getIt<MapBloc>()),
|
||||||
|
BlocProvider(create: (context) => getIt<ProfileBloc>()),
|
||||||
],
|
],
|
||||||
child: BlocListener<SplashBloc, SplashState>(
|
child: BlocListener<SplashBloc, SplashState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
|||||||
244
lib/presentation/components/content_parser.dart
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:html/parser.dart' as html_parser;
|
||||||
|
import 'package:html/dom.dart' as dom;
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
/// Парсер контента новостей (поддерживает HTML и JSON-формат)
|
||||||
|
class ContentParser {
|
||||||
|
|
||||||
|
/// Парсит текст новости: сначала проверяет textJson, потом text
|
||||||
|
static List<Widget> parseContent({String? textJson, String? text}) {
|
||||||
|
if (textJson != null && textJson.isNotEmpty) {
|
||||||
|
return _parseJsonText(textJson);
|
||||||
|
} else if (text != null && text.isNotEmpty) {
|
||||||
|
return _parseHtmlText(text);
|
||||||
|
}
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 HTML-парсинг
|
||||||
|
static List<Widget> _parseHtmlText(String htmlText) {
|
||||||
|
final parsedHtml = html_parser.parse(htmlText);
|
||||||
|
final List<dom.Node> elements = parsedHtml.body?.nodes ?? [];
|
||||||
|
return _parseHtmlElements(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Widget> _parseHtmlElements(List<dom.Node> nodes) {
|
||||||
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
|
for (final node in nodes) {
|
||||||
|
if (node is dom.Element) {
|
||||||
|
final text = node.text.trim();
|
||||||
|
if (text.isEmpty) continue;
|
||||||
|
|
||||||
|
switch (node.localName) {
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
widgets.add(_buildHeader(text));
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
widgets.add(_buildParagraph(text));
|
||||||
|
break;
|
||||||
|
case 'ul':
|
||||||
|
widgets.add(_buildUnorderedList(node.nodes));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
widgets.add(_buildDefaultText(text));
|
||||||
|
}
|
||||||
|
} else if (node is dom.Text) {
|
||||||
|
final text = node.text.trim();
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
widgets.add(_buildDefaultText(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 JSON-парсинг
|
||||||
|
static List<Widget> _parseJsonText(String jsonString) {
|
||||||
|
try {
|
||||||
|
final dynamic data = jsonDecode(jsonString);
|
||||||
|
return _parseJsonNode(data);
|
||||||
|
} catch (e) {
|
||||||
|
return [
|
||||||
|
const Text(
|
||||||
|
'Ошибка отображения текста новости',
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 14),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Widget> _parseJsonNode(dynamic node) {
|
||||||
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
|
if (node is List) {
|
||||||
|
for (final item in node) {
|
||||||
|
widgets.addAll(_parseJsonNode(item));
|
||||||
|
}
|
||||||
|
} else if (node is Map<String, dynamic>) {
|
||||||
|
final type = node['type'] as String?;
|
||||||
|
final content = node['content'];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'div':
|
||||||
|
if (content is List) {
|
||||||
|
widgets.addAll(_parseJsonNode(content));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
final text = _extractJsonText(content);
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
widgets.add(_buildHeader(text));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
final text = _extractJsonText(content);
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
widgets.add(_buildParagraph(text));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ul':
|
||||||
|
if (content is List) {
|
||||||
|
widgets.add(_buildJsonUnorderedList(content));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'li':
|
||||||
|
final text = _extractJsonText(content);
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
widgets.add(_buildListItem(text));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
final text = _extractJsonText(content);
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
widgets.add(_buildDefaultText(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Вспомогательные методы извлечения текста из JSON
|
||||||
|
static String? _extractJsonText(dynamic content) {
|
||||||
|
if (content == null) return null;
|
||||||
|
if (content is List) {
|
||||||
|
return content.map((e) => e?.toString() ?? '').where((s) => s.isNotEmpty).join(' ');
|
||||||
|
}
|
||||||
|
return content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Виджеты для отображения (принимают НЕ nullable String)
|
||||||
|
static Widget _buildHeader(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
text, // ✅ text — не nullable
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildParagraph(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildDefaultText(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildUnorderedList(List<dom.Node> children) {
|
||||||
|
final items = <Widget>[];
|
||||||
|
for (final child in children) {
|
||||||
|
if (child is dom.Element && child.localName == 'li') {
|
||||||
|
final text = child.text.trim();
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
items.add(_buildListItem(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: items,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildJsonUnorderedList(List<dynamic> content) {
|
||||||
|
final items = <Widget>[];
|
||||||
|
for (final item in content) {
|
||||||
|
if (item is Map<String, dynamic> && item['type'] == 'li') {
|
||||||
|
final text = _extractJsonText(item['content']);
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
items.add(_buildListItem(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: items,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildListItem(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('• ',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
class CancelBookingDialog extends StatelessWidget {
|
class CancelBookingDialog extends StatelessWidget {
|
||||||
const CancelBookingDialog({super.key});
|
const CancelBookingDialog({super.key});
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
insetPadding: const EdgeInsets.symmetric(horizontal: 40),
|
insetPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
@@ -42,6 +39,7 @@ class CancelBookingDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 🔹 Кнопка "Отменить" — ПЕРВАЯ, зелёная (градиент), возвращает true
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 52,
|
height: 52,
|
||||||
@@ -52,10 +50,7 @@ class CancelBookingDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => {
|
onPressed: () => Navigator.pop(context, true), // ✅ true = отменить
|
||||||
result = false,
|
|
||||||
Navigator.pop(context, result)
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
@@ -64,9 +59,9 @@ class CancelBookingDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"Оставить",
|
"Отменить",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF1D273A),
|
color: Color(0xFF1D273A), // тёмный текст на светлом градиенте
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -74,14 +69,13 @@ class CancelBookingDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// 🔹 Кнопка "Оставить" — ВТОРАЯ, тёмная, возвращает false
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 52,
|
height: 52,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context, false), // ❌ false = оставить
|
||||||
result = true;
|
|
||||||
Navigator.pop(context, result);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF0D1024),
|
backgroundColor: const Color(0xFF0D1024),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -89,7 +83,7 @@ class CancelBookingDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"Отменить",
|
"Оставить",
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FinishRideConfirmationDialog extends StatelessWidget {
|
||||||
|
const FinishRideConfirmationDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2E3253).withOpacity(0.95),
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Завершить поездку?",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
"Вы действительно хотите завершить поездку?",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 🔹 Кнопка "Завершить" — ПЕРВАЯ, зелёная (градиент), возвращает true
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 52,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF8EFEB5), Color(0xFF86FEF1)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true), // ✅ true = завершить
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"Завершить",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF1D273A), // тёмный текст на светлом градиенте
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// 🔹 Кнопка "Отмена" — ВТОРАЯ, тёмная, возвращает false
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 52,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false), // ❌ false = отмена
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF0D1024),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
"Отмена",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
import 'package:be_happy/presentation/event/map_event.dart';
|
|
||||||
import 'package:be_happy/presentation/event/map_settings_modal_event.dart';
|
|
||||||
import 'package:be_happy/presentation/state/map_settings_modal_state.dart';
|
|
||||||
import 'package:be_happy/presentation/viewmodel/map_settings_modal_bloc.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
import '../viewmodel/map_bloc.dart';
|
|
||||||
|
|
||||||
class MapSettingsSheet extends StatelessWidget {
|
|
||||||
final VoidCallback? onClose;
|
|
||||||
|
|
||||||
const MapSettingsSheet({super.key, this.onClose});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<MapSettingsModalBloc, MapSettingsModalState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final List<_SettingItemData> items = [
|
|
||||||
_SettingItemData(
|
|
||||||
label: 'Геоточки',
|
|
||||||
icon: Icons.location_on_outlined,
|
|
||||||
color: const Color(0xFF66E3C4),
|
|
||||||
isActive: state.isAllGeomarksActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(AllGeomarksToggled(val)),
|
|
||||||
),
|
|
||||||
_SettingItemData(
|
|
||||||
label: 'Геозоны',
|
|
||||||
icon: Icons.gps_fixed_outlined,
|
|
||||||
color: const Color(0xFF86EFAC),
|
|
||||||
isActive: state.isAllGeozonesActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(AllGeozonesToggled(val)),
|
|
||||||
),
|
|
||||||
_SettingItemData(
|
|
||||||
label: 'Парковка',
|
|
||||||
icon: Icons.home_outlined,
|
|
||||||
color: const Color(0xFFA78BFA),
|
|
||||||
isActive: state.isParkingZoneActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(ParkingZonesToggled(val)),
|
|
||||||
),
|
|
||||||
_SettingItemData(
|
|
||||||
label: 'Парковка запрещена',
|
|
||||||
icon: Icons.block_outlined,
|
|
||||||
color: const Color(0xFFF59E0B),
|
|
||||||
isActive: state.isRestrictedParkingZoneActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(RestrictedParkingZonesToggled(val)),
|
|
||||||
),
|
|
||||||
_SettingItemData(
|
|
||||||
label: 'Запрещено кататься',
|
|
||||||
icon: Icons.warning_amber_outlined,
|
|
||||||
color: const Color(0xFFEF4444),
|
|
||||||
isActive: state.isRestrictedDrivingZoneActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(RestrictedDrivingZonesToggled(val)),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
|
||||||
child: Container(
|
|
||||||
height: 365,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF000032).withOpacity(0.88),
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Параметры карты',
|
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<MapSettingsModalBloc>().add(ApllyButtonClick());
|
|
||||||
context.read<MapBloc>().add(UpdateMap());
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'Готово',
|
|
||||||
style: TextStyle(color: Color(0xFF66E3C4), fontSize: 16, fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
itemCount: items.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = items[index];
|
|
||||||
return ListTile(
|
|
||||||
leading: Icon(item.icon, color: item.color),
|
|
||||||
title: Text(item.label, style: const TextStyle(color: Colors.white)),
|
|
||||||
trailing: Switch.adaptive(
|
|
||||||
value: item.isActive,
|
|
||||||
onChanged: item.onChanged,
|
|
||||||
activeTrackColor: const Color(0xFF66E3C4),
|
|
||||||
inactiveThumbColor: Colors.white,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательный класс для описания строк
|
|
||||||
class _SettingItemData {
|
|
||||||
final String label;
|
|
||||||
final IconData icon;
|
|
||||||
final Color color;
|
|
||||||
final bool isActive;
|
|
||||||
final ValueChanged<bool> onChanged;
|
|
||||||
|
|
||||||
_SettingItemData({
|
|
||||||
required this.label,
|
|
||||||
required this.icon,
|
|
||||||
required this.color,
|
|
||||||
required this.isActive,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,83 @@ import 'package:flutter/material.dart';
|
|||||||
import 'scooter_info_item.dart';
|
import 'scooter_info_item.dart';
|
||||||
|
|
||||||
class ScooterInfoSection extends StatelessWidget {
|
class ScooterInfoSection extends StatelessWidget {
|
||||||
const ScooterInfoSection({super.key});
|
final double distance;
|
||||||
|
final String location;
|
||||||
|
final int batteryLevel;
|
||||||
|
|
||||||
|
const ScooterInfoSection({
|
||||||
|
super.key,
|
||||||
|
required this.distance,
|
||||||
|
required this.location,
|
||||||
|
required this.batteryLevel,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// 1. Расчет оставшегося расстояния (макс. 62 км)
|
||||||
|
final double remainingKm = (62 * batteryLevel) / 100;
|
||||||
|
|
||||||
|
// Красиво форматируем: если число целое — убираем точку (например, 31 км вместо 31.0 км)
|
||||||
|
final String kmText = remainingKm % 1 == 0
|
||||||
|
? '${remainingKm.toInt()} км'
|
||||||
|
: '${remainingKm.toStringAsFixed(1)} км';
|
||||||
|
|
||||||
|
// 2. Расчет оставшегося времени (макс. 2.5 часа = 150 минут)
|
||||||
|
final int totalMinutes = ((150 * batteryLevel) / 100).round();
|
||||||
|
final int hours = totalMinutes ~/ 60;
|
||||||
|
final int minutes = totalMinutes % 60;
|
||||||
|
|
||||||
|
// Формируем строку времени в зависимости от получившихся значений
|
||||||
|
String timeText = '';
|
||||||
|
if (hours > 0 && minutes > 0) {
|
||||||
|
timeText = '$hours ч $minutes мин';
|
||||||
|
} else if (hours > 0) {
|
||||||
|
timeText = '$hours ч';
|
||||||
|
} else {
|
||||||
|
timeText = '$minutes мин';
|
||||||
|
}
|
||||||
|
|
||||||
|
final int walkingMinutes = (distance / 80).round().clamp(1, 999);
|
||||||
|
final int walkHours = walkingMinutes ~/ 60;
|
||||||
|
final int walkMins = walkingMinutes % 60;
|
||||||
|
|
||||||
|
String walkingText = '';
|
||||||
|
if (walkHours > 0 && walkMins > 0) {
|
||||||
|
walkingText = '$walkHours ч $walkMins мин';
|
||||||
|
} else if (walkHours > 0) {
|
||||||
|
walkingText = '$walkHours ч';
|
||||||
|
} else {
|
||||||
|
walkingText = '$walkingMinutes мин';
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: const [
|
children: [
|
||||||
ScooterInfoItem(icon: 'assets/icons/bolt.png', text: '47 км или 4 часа 17 минут'),
|
// Динамическая строка с зарядом
|
||||||
ScooterInfoItem(icon: 'assets/icons/speed.png', text: 'max = 25 км/ч'),
|
ScooterInfoItem(
|
||||||
ScooterInfoItem(icon: 'assets/icons/location.png', text: 'пр. Московский, 33'),
|
icon: 'assets/icons/bolt.png',
|
||||||
|
text: '$kmText или $timeText',
|
||||||
|
),
|
||||||
|
ScooterInfoItem(
|
||||||
|
icon: 'assets/icons/speed.png',
|
||||||
|
text: 'max = 25 км/ч',
|
||||||
|
),
|
||||||
|
ScooterInfoItem(
|
||||||
|
icon: 'assets/icons/location.png',
|
||||||
|
text: location,
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
ScooterInfoItem(icon: 'assets/icons/person.png', text: '120 м'),
|
ScooterInfoItem(
|
||||||
SizedBox(width: 16),
|
icon: 'assets/icons/person.png',
|
||||||
ScooterInfoItem(icon: 'assets/icons/time.png', text: '1 минута'),
|
text: distance.toInt() >= 1000
|
||||||
|
? '${((distance.toInt()) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${distance.toInt()} m',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
ScooterInfoItem(
|
||||||
|
icon: 'assets/icons/time.png',
|
||||||
|
text: walkingText,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
child: _ScooterCard(
|
child: _ScooterCard(
|
||||||
scooter: scooter,
|
scooter: scooter,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
address: state.address ?? "Unknown address",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,8 +152,9 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
class _ScooterCard extends StatelessWidget {
|
class _ScooterCard extends StatelessWidget {
|
||||||
final Scooter scooter;
|
final Scooter scooter;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
final String address;
|
||||||
|
|
||||||
const _ScooterCard({required this.scooter, required this.isActive});
|
const _ScooterCard({required this.scooter, required this.isActive, required this.address});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -186,7 +188,9 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
"${(scooter.distance?.toInt()) ?? 0}m",
|
(scooter.distance?.toInt() ?? 0) >= 1000
|
||||||
|
? '${((scooter.distance?.toInt() ?? 0) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${scooter.distance?.toInt() ?? 0} m',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -229,7 +233,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go('/home/scooter/${scooter.id}');
|
context.go('/home/scooter/${scooter.id}', extra: [address, scooter.distance]);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 42,
|
height: 42,
|
||||||
@@ -241,7 +245,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go('/home/scooter/${scooter.id}');
|
context.go('/home/scooter/${scooter.id}', extra: [address, scooter.distance]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
import 'package:be_happy/domain/entities/scooter.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../../../di/service_locator.dart';
|
import '../../../di/service_locator.dart';
|
||||||
import '../../event/active_ride_event.dart';
|
import '../../event/active_ride_event.dart';
|
||||||
|
import '../../event/map_event.dart';
|
||||||
import '../../state/active_ride_state.dart';
|
import '../../state/active_ride_state.dart';
|
||||||
import '../../viewmodel/active_ride_bloc.dart';
|
import '../../viewmodel/active_ride_bloc.dart';
|
||||||
|
import '../../viewmodel/map_bloc.dart';
|
||||||
|
import '../dialog/finish_ride_confirmation_dialog.dart';
|
||||||
import '../notification_toast.dart';
|
import '../notification_toast.dart';
|
||||||
|
|
||||||
class ActiveRideSheet extends StatefulWidget {
|
class ActiveRideSheet extends StatefulWidget {
|
||||||
@@ -55,7 +59,7 @@ class _ActiveRideSheetState extends State<ActiveRideSheet> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _bloc,
|
value: _bloc,
|
||||||
child: BlocConsumer<ActiveRideBloc, ActiveRideState>(
|
child: BlocConsumer<ActiveRideBloc, ActiveRideState>(
|
||||||
listenWhen: (previous, current) => previous.inZone != current.inZone,
|
listenWhen: (previous, current) => previous.inZone != current.inZone || previous.status != current.status,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (!state.inZone) {
|
if (!state.inZone) {
|
||||||
BotToast.showCustomNotification(
|
BotToast.showCustomNotification(
|
||||||
@@ -70,6 +74,15 @@ class _ActiveRideSheetState extends State<ActiveRideSheet> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.status == ActiveRideStatus.success && state.order != null) {
|
||||||
|
final scooter = state.order!.scooter;
|
||||||
|
context.read<MapBloc>().add(FocusOnScooter(Scooter(id: scooter.id,
|
||||||
|
title: scooter.title, status: scooter.status,
|
||||||
|
latitude: state.longitude, longitude: state.latitude,
|
||||||
|
batteryLevel: scooter.batteryLevel, isOnline: scooter.isOnline,
|
||||||
|
maxSpeed: scooter.maxSpeed, number: scooter.number)));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// Логика отображения загрузки и ошибок остается прежней
|
// Логика отображения загрузки и ошибок остается прежней
|
||||||
@@ -202,7 +215,7 @@ class _ActiveRideSheetState extends State<ActiveRideSheet> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontFeatures: [FontFeature.tabularFigures()],
|
fontFeatures: const [FontFeature.tabularFigures()],
|
||||||
fontFamily: 'DigitalNumbers',
|
fontFamily: 'DigitalNumbers',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -290,13 +303,23 @@ class _ActiveRideSheetState extends State<ActiveRideSheet> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: state.status == ActiveRideStatus.loading
|
onTap: state.status == ActiveRideStatus.loading
|
||||||
? null
|
? null
|
||||||
: () {
|
: () async {
|
||||||
|
// 🔹 Показываем диалог подтверждения
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const FinishRideConfirmationDialog(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 🔹 Если пользователь подтвердил — завершаем и переходим
|
||||||
|
if (result == true) {
|
||||||
_bloc.add(FinishRide(widget.orderId));
|
_bloc.add(FinishRide(widget.orderId));
|
||||||
Navigator.pop(context);
|
Navigator.pop(context); // закрываем ActiveRideSheet
|
||||||
context.go("/home/order-photos/${widget.orderId}");
|
context.go("/home/order-photos/${widget.orderId}");
|
||||||
|
}
|
||||||
|
// 🔹 Если отменил — ничего не делаем, диалог уже закрылся
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Column(
|
child: Column( // ✅ Вернули Column с иконкой и текстом
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
|
|||||||
@@ -218,12 +218,15 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
} else {
|
} else {
|
||||||
displayTime = _elapsedTime;
|
displayTime = _elapsedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isExpired = isReserved && (_reservationTime - _elapsedTime).isNegative;
|
||||||
|
|
||||||
final timeString = _formatDuration(displayTime);
|
final timeString = _formatDuration(displayTime);
|
||||||
final statusText = _getStatusText(widget.order.status);
|
final statusText = widget.order.status == 'Booking' ? 'Забронирован' : "Активный";
|
||||||
final statusColor = _getStatusColor(widget.order.status);
|
final statusColor = widget.order.status == 'Booking' ? Color(0xFFFFCC00) : Color(0xFF8bffaa);
|
||||||
|
|
||||||
final scooterNumber =
|
final scooterNumber =
|
||||||
widget.order.scooter?.number ?? widget.order.scooterId.toString();
|
widget.order.scooter.number ?? widget.order.scooterId.toString();
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -261,7 +264,7 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
Text(
|
Text(
|
||||||
statusText,
|
statusText,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: statusColor,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
@@ -269,6 +272,10 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Image.asset("assets/icons/qr_icon_order.png"),
|
||||||
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
scooterNumber,
|
scooterNumber,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -277,14 +284,39 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (isReserved)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
_getLocationText(),
|
"Тариф",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withOpacity(0.6),
|
color: Color(0xFF8bffaa),
|
||||||
fontSize: 13,
|
fontSize: 11,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset("assets/icons/timer.png", width: 14,),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
widget.order.plan?.title ?? "Название тарифа",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF8bffaa),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -301,7 +333,7 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
timeString,
|
timeString,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
fontSize: 26,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontFeatures: const [FontFeature.tabularFigures()],
|
fontFeatures: const [FontFeature.tabularFigures()],
|
||||||
fontFamily: 'DigitalNumbers',
|
fontFamily: 'DigitalNumbers',
|
||||||
@@ -318,7 +350,8 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
height: 32,
|
height: 32,
|
||||||
width: 100,
|
width: 100,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
onTap: () {
|
enabled: !isExpired,
|
||||||
|
onTap: !isExpired ? () {
|
||||||
if (isReserved) {
|
if (isReserved) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -345,7 +378,7 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
} : null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -360,7 +393,7 @@ class _RideCardState extends State<_RideCard> {
|
|||||||
switch (status.toLowerCase()) {
|
switch (status.toLowerCase()) {
|
||||||
case 'reserved':
|
case 'reserved':
|
||||||
case 'holding':
|
case 'holding':
|
||||||
return 'Забронировано';
|
return 'Забронирован';
|
||||||
case 'active':
|
case 'active':
|
||||||
case 'in_progress':
|
case 'in_progress':
|
||||||
return 'Активно';
|
return 'Активно';
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import 'package:be_happy/presentation/state/map_settings_modal_state.dart';
|
|||||||
import 'package:be_happy/presentation/viewmodel/map_settings_modal_bloc.dart';
|
import 'package:be_happy/presentation/viewmodel/map_settings_modal_bloc.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
import '../../viewmodel/map_bloc.dart';
|
import '../../viewmodel/map_bloc.dart';
|
||||||
|
|
||||||
class MapSettingsSheet extends StatelessWidget {
|
class MapSettingsSheet extends StatelessWidget {
|
||||||
@@ -19,13 +17,6 @@ class MapSettingsSheet extends StatelessWidget {
|
|||||||
return BlocBuilder<MapSettingsModalBloc, MapSettingsModalState>(
|
return BlocBuilder<MapSettingsModalBloc, MapSettingsModalState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final List<_SettingItemData> items = [
|
final List<_SettingItemData> items = [
|
||||||
_SettingItemData(
|
|
||||||
label: 'Геоточки',
|
|
||||||
icon: Icons.location_on_outlined,
|
|
||||||
color: const Color(0xFF66E3C4),
|
|
||||||
isActive: state.isAllGeomarksActive,
|
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(AllGeomarksToggled(val)),
|
|
||||||
),
|
|
||||||
_SettingItemData(
|
_SettingItemData(
|
||||||
label: 'Геозоны',
|
label: 'Геозоны',
|
||||||
icon: Icons.gps_fixed_outlined,
|
icon: Icons.gps_fixed_outlined,
|
||||||
@@ -41,9 +32,9 @@ class MapSettingsSheet extends StatelessWidget {
|
|||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(ParkingZonesToggled(val)),
|
onChanged: (val) => context.read<MapSettingsModalBloc>().add(ParkingZonesToggled(val)),
|
||||||
),
|
),
|
||||||
_SettingItemData(
|
_SettingItemData(
|
||||||
label: 'Разрешено кататься',
|
label: 'Парковка запрещена',
|
||||||
icon: Icons.block_outlined,
|
icon: Icons.block_outlined,
|
||||||
color: const Color(0xFF5ECD4C),
|
color: const Color(0xFFF59E0B),
|
||||||
isActive: state.isRestrictedParkingZoneActive,
|
isActive: state.isRestrictedParkingZoneActive,
|
||||||
onChanged: (val) => context.read<MapSettingsModalBloc>().add(RestrictedParkingZonesToggled(val)),
|
onChanged: (val) => context.read<MapSettingsModalBloc>().add(RestrictedParkingZonesToggled(val)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import '../../state/payment_method_sheet_state.dart';
|
|||||||
import '../../viewmodel/payment_method_sheet_bloc.dart';
|
import '../../viewmodel/payment_method_sheet_bloc.dart';
|
||||||
|
|
||||||
class PaymentMethodSheet extends StatefulWidget {
|
class PaymentMethodSheet extends StatefulWidget {
|
||||||
final PaymentCard? initialSelectedCard; // Добавляем это поле
|
final PaymentCard? initialSelectedCard;
|
||||||
|
final bool showBalance;
|
||||||
|
|
||||||
const PaymentMethodSheet({
|
const PaymentMethodSheet({
|
||||||
super.key,
|
super.key,
|
||||||
this.initialSelectedCard, // Инициализируем в конструкторе
|
this.initialSelectedCard,
|
||||||
|
this.showBalance = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -25,6 +27,7 @@ class PaymentMethodSheet extends StatefulWidget {
|
|||||||
|
|
||||||
class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
||||||
int? _selectedPaymentMethod = -2;
|
int? _selectedPaymentMethod = -2;
|
||||||
|
int? _processingCardId; // <-- Переменная для отслеживания отправленного запроса
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -34,56 +37,20 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<PaymentMethodSheetBloc, PaymentMethodSheetState>(
|
return BlocConsumer<PaymentMethodSheetBloc, PaymentMethodSheetState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.status == PaymentMethodSheetStatus.success && _processingCardId != null) {
|
||||||
|
final updatedCard = state.cards.cast<PaymentCard?>().firstWhere(
|
||||||
|
(c) => c?.id == _processingCardId,
|
||||||
|
orElse: () => state.cards.cast<PaymentCard?>().firstWhere((c) => c?.isMain == true, orElse: () => null),
|
||||||
|
);
|
||||||
|
|
||||||
|
_processingCardId = null;
|
||||||
|
Navigator.pop(context, updatedCard);
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
|
||||||
if (state.status == PaymentMethodSheetStatus.loading) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
|
||||||
child: Container(
|
|
||||||
height: 450,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0x00000032).withOpacity(0.6),
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: CircularProgressIndicator(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.status == PaymentMethodSheetStatus.failure) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
|
||||||
child: Container(
|
|
||||||
height: 450,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0x00000032).withOpacity(0.6),
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
state.errorMessage ?? 'Ошибка загрузки карт',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.status == PaymentMethodSheetStatus.success && _selectedPaymentMethod == -2) {
|
if (state.status == PaymentMethodSheetStatus.success && _selectedPaymentMethod == -2) {
|
||||||
if (widget.initialSelectedCard != null) {
|
if (widget.initialSelectedCard != null) {
|
||||||
final initialIndex = state.cards.indexWhere(
|
final initialIndex = state.cards.indexWhere(
|
||||||
@@ -92,18 +59,10 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
_selectedPaymentMethod = initialIndex != -1 ? initialIndex : -1;
|
_selectedPaymentMethod = initialIndex != -1 ? initialIndex : -1;
|
||||||
} else {
|
} else {
|
||||||
final mainCardIndex = state.cards.indexWhere((card) => card.isMain);
|
final mainCardIndex = state.cards.indexWhere((card) => card.isMain);
|
||||||
_selectedPaymentMethod = mainCardIndex != -1 ? mainCardIndex : -1;
|
_selectedPaymentMethod = mainCardIndex != -1 ? mainCardIndex : (widget.showBalance ? -1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Находим карту с isMain: true при загрузке
|
|
||||||
/*if (_selectedPaymentMethod == null) {
|
|
||||||
final mainCardIndex = state.cards.indexWhere((card) => card.isMain);
|
|
||||||
if (mainCardIndex != -1) {
|
|
||||||
_selectedPaymentMethod = mainCardIndex;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@@ -120,47 +79,6 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_back_ios_sharp,
|
|
||||||
color: const Color(0x99FFFFFF),
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_back_ios_sharp,
|
|
||||||
color: const Color(0x66FFFFFF),
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_back_ios_sharp,
|
|
||||||
color: const Color(0x22FFFFFF),
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Выберите способ оплаты',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
@@ -169,9 +87,10 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.showBalance) ...[
|
||||||
PaymentOption(
|
PaymentOption(
|
||||||
title: 'Баланс',
|
title: 'Баланс',
|
||||||
subtitle: '${state.balance.toStringAsFixed(2)} BYN',
|
subtitle: '${state.balance} баллов',
|
||||||
isSelected: _selectedPaymentMethod == -1,
|
isSelected: _selectedPaymentMethod == -1,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -180,8 +99,8 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
Navigator.pop(context, 'balance');
|
Navigator.pop(context, 'balance');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
|
||||||
...state.cards.asMap().entries.map((entry) {
|
...state.cards.asMap().entries.map((entry) {
|
||||||
final index = entry.key;
|
final index = entry.key;
|
||||||
@@ -189,14 +108,20 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: card.type,
|
title: card.isMain ? "Основная" : "Карта",
|
||||||
subtitle: '****${card.cardLastNumber}',
|
subtitle: '${card.cardLastNumber}',
|
||||||
isSelected: _selectedPaymentMethod == index,
|
isSelected: _selectedPaymentMethod == index,
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPaymentMethod = index;
|
_selectedPaymentMethod = index;
|
||||||
|
_processingCardId = card.id;
|
||||||
});
|
});
|
||||||
Navigator.pop(context, card);
|
|
||||||
|
// Отправляем событие в Блок
|
||||||
|
context.read<PaymentMethodSheetBloc>().add(
|
||||||
|
SelectPaymentCardRequested(card),
|
||||||
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -215,8 +140,7 @@ class _PaymentMethodSheetState extends State<PaymentMethodSheet> {
|
|||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
context.read<PaymentMethodSheetBloc>().add(AddPaymentCardRequested());
|
||||||
context.go('/home/payment-methods/add-card');
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _bloc,
|
value: _bloc,
|
||||||
child: Align(
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
body: Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
||||||
@@ -70,7 +73,9 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
padding: const EdgeInsets.only(top: 20, bottom: 10),
|
padding: const EdgeInsets.only(top: 20, bottom: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF000032).withOpacity(0.5),
|
color: const Color(0xFF000032).withOpacity(0.5),
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(30),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -143,7 +148,10 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.1),
|
color: Colors.white.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -163,7 +171,8 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
// Инфо
|
// Инфо
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@@ -228,9 +237,12 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
orderId: widget.orderId,
|
orderId: widget.orderId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (state.status == ReservedRideStatus.failure) {
|
} else if (state.status ==
|
||||||
|
ReservedRideStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Ошибка')),
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage ?? 'Ошибка'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -256,9 +268,12 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.rideCancelled) {
|
if (state.rideCancelled) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
} else if (state.status == ReservedRideStatus.failure) {
|
} else if (state.status ==
|
||||||
|
ReservedRideStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.errorMessage ?? 'Ошибка')),
|
SnackBar(
|
||||||
|
content: Text(state.errorMessage ?? 'Ошибка'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -277,16 +292,22 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showDialog<bool>(
|
final result = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const CancelBookingDialog(),
|
builder: (context) =>
|
||||||
|
const CancelBookingDialog(),
|
||||||
);
|
);
|
||||||
if (result != null && result) {
|
if (result != null && result) {
|
||||||
_bloc.add(CancelRide(widget.orderId));
|
_bloc.add(CancelRide(widget.orderId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
child: BlocBuilder<ReservedRideBloc, ReservedRideState>(
|
child:
|
||||||
|
BlocBuilder<
|
||||||
|
ReservedRideBloc,
|
||||||
|
ReservedRideState
|
||||||
|
>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.status == ReservedRideStatus.loading) {
|
if (state.status ==
|
||||||
|
ReservedRideStatus.loading) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
@@ -323,6 +344,7 @@ class _ReservedRideSheetState extends State<ReservedRideSheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
child: _ScooterCard(
|
child: _ScooterCard(
|
||||||
scooter: scooter,
|
scooter: scooter,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
address: state.address ?? "Unknown address",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -174,9 +175,10 @@ class _ScooterBottomSheetState extends State<ScooterBottomSheet> {
|
|||||||
|
|
||||||
class _ScooterCard extends StatelessWidget {
|
class _ScooterCard extends StatelessWidget {
|
||||||
final Scooter scooter;
|
final Scooter scooter;
|
||||||
|
final String address;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
const _ScooterCard({required this.scooter, required this.isActive});
|
const _ScooterCard({required this.scooter, required this.isActive, required this.address});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -210,7 +212,9 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
"${(scooter.distance?.toInt()) ?? 0}m",
|
(scooter.distance?.toInt() ?? 0) >= 1000
|
||||||
|
? '${((scooter.distance?.toInt() ?? 0) / 1000).toStringAsFixed(1)} km'
|
||||||
|
: '${scooter.distance?.toInt() ?? 0} m',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -262,7 +266,7 @@ class _ScooterCard extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, scooter);
|
Navigator.pop(context, [scooter, address]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import '../../../di/service_locator.dart';
|
|||||||
import '../../../domain/entities/payment_card.dart';
|
import '../../../domain/entities/payment_card.dart';
|
||||||
import '../../../domain/entities/scooter.dart';
|
import '../../../domain/entities/scooter.dart';
|
||||||
import '../../../domain/entities/tariff.dart';
|
import '../../../domain/entities/tariff.dart';
|
||||||
|
import '../../../domain/usecase/add_payment_card_usecase.dart';
|
||||||
import '../../../domain/usecase/get_payment_cards_usecase.dart';
|
import '../../../domain/usecase/get_payment_cards_usecase.dart';
|
||||||
|
import '../../../domain/usecase/get_profile_usecase.dart';
|
||||||
|
import '../../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||||
import '../../event/payment_method_sheet_event.dart';
|
import '../../event/payment_method_sheet_event.dart';
|
||||||
import '../../event/tariff_sheet_event.dart';
|
import '../../event/tariff_sheet_event.dart';
|
||||||
import '../../state/tariff_sheet_state.dart';
|
import '../../state/tariff_sheet_state.dart';
|
||||||
@@ -39,6 +42,24 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final double remainingKm = (62 * widget.scooter.batteryLevel) / 100;
|
||||||
|
|
||||||
|
final String kmText = remainingKm % 1 == 0
|
||||||
|
? '${remainingKm.toInt()} км'
|
||||||
|
: '${remainingKm.toStringAsFixed(1)} км';
|
||||||
|
|
||||||
|
final int totalMinutes = ((150 * widget.scooter.batteryLevel) / 100).round();
|
||||||
|
final int hours = totalMinutes ~/ 60;
|
||||||
|
final int minutes = totalMinutes % 60;
|
||||||
|
|
||||||
|
String timeText = '';
|
||||||
|
if (hours > 0 && minutes > 0) {
|
||||||
|
timeText = '$hours ч $minutes мин';
|
||||||
|
} else if (hours > 0) {
|
||||||
|
timeText = '$hours ч';
|
||||||
|
} else {
|
||||||
|
timeText = '$minutes мин';
|
||||||
|
}
|
||||||
return BlocConsumer<TariffSheetBloc, TariffSheetState>(
|
return BlocConsumer<TariffSheetBloc, TariffSheetState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == TariffSheetStatus.failure && state.errorMessage != null) {
|
if (state.status == TariffSheetStatus.failure && state.errorMessage != null) {
|
||||||
@@ -134,7 +155,6 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 🔹 HEADER
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -226,7 +246,7 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Заряда хватит на 4 часа 17 минут\nили 47 км',
|
'Заряда хватит на $kmText или $timeText',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -288,10 +308,10 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
child: PaymentOption(
|
child: PaymentOption(
|
||||||
title: state.useBalance
|
title: state.useBalance
|
||||||
? 'Баланс'
|
? 'Баланс'
|
||||||
: state.selectedCard!.type,
|
: "Карта",
|
||||||
subtitle: state.useBalance
|
subtitle: state.useBalance
|
||||||
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
? '${state.userBalance.toStringAsFixed(2)} BYN'
|
||||||
: '****${state.selectedCard!.cardLastNumber}',
|
: '${state.selectedCard!.cardLastNumber}',
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showModalBottomSheet<dynamic>(
|
final result = await showModalBottomSheet<dynamic>(
|
||||||
@@ -301,6 +321,9 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
builder: (innerContext) => BlocProvider(
|
builder: (innerContext) => BlocProvider(
|
||||||
create: (context) => PaymentMethodSheetBloc(
|
create: (context) => PaymentMethodSheetBloc(
|
||||||
getIt<GetPaymentCardsUsecase>(),
|
getIt<GetPaymentCardsUsecase>(),
|
||||||
|
getIt<SetMainPaymentCardUsecase>(),
|
||||||
|
getIt<GetProfileUseCase>(),
|
||||||
|
getIt<AddPaymentCardUsecase>(),
|
||||||
)..add(PaymentMethodSheetStarted()),
|
)..add(PaymentMethodSheetStarted()),
|
||||||
child: PaymentMethodSheet(
|
child: PaymentMethodSheet(
|
||||||
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
initialSelectedCard: state.useBalance ? null : state.selectedCard,
|
||||||
@@ -381,7 +404,6 @@ class _TariffSheetState extends State<TariffSheet> {
|
|||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// 🔹 КНОПКА "ЗАБРОНИРОВАТЬ"
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: GradientButton(
|
child: GradientButton(
|
||||||
@@ -494,13 +516,10 @@ class _TariffCard extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Резервируем место под иконку инфо справа,
|
|
||||||
// чтобы текст не залез под неё
|
|
||||||
const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Цена + текст рядом
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
@@ -540,7 +559,6 @@ class _TariffCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Кнопка-иконка в верхнем правом углу
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -550,7 +568,7 @@ class _TariffCard extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder: (context) => TariffInfoSheet(
|
builder: (_) => TariffInfoSheet(
|
||||||
tariff: tariff,
|
tariff: tariff,
|
||||||
isInsurance: isInsurance,
|
isInsurance: isInsurance,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
|
|||||||