forked from eatvio/eatvio_app
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0ac156307 | ||
|
|
4a9d45a18d | ||
|
|
65fabefdb9 | ||
|
|
f573bb28f8 | ||
|
|
20aca4b9a4 | ||
|
|
5354c3f517 | ||
| 17dbc1d5bf | |||
|
|
09a00a41ad | ||
|
|
24675714e0 | ||
|
|
21492a1ad9 | ||
|
|
239ff675db | ||
|
|
fa6c2f9000 | ||
|
|
ae0c3a53be | ||
| 21555bfb10 | |||
|
|
f90f1d3284 | ||
| f9fed539c2 | |||
|
|
654edc5457 | ||
| 928a55ae93 | |||
|
|
b2433b87b6 | ||
|
|
183fa012d2 | ||
|
|
9ac576221a | ||
|
|
5013ffe06b | ||
| 2993c05dd1 | |||
|
|
87c0d4ec2a | ||
|
|
17be198a23 | ||
|
|
79de6ee6fa | ||
|
|
b0d2d86a18 | ||
|
|
03448ebd2d | ||
| d87d0e5e2a | |||
|
|
5275273830 | ||
|
|
584f00a1ea | ||
|
|
37cedb2238 | ||
|
|
09d025252d | ||
|
|
086e6cf94e | ||
|
|
766d5ba248 | ||
|
|
e7ce9d8f90 | ||
|
|
7861cdce3c | ||
|
|
2865852124 | ||
|
|
d18ad86590 | ||
|
|
f1945202fe | ||
|
|
b65fca8116 | ||
|
|
27376ca54d | ||
|
|
01f1c39450 | ||
|
|
24fe984e6b | ||
|
|
053cf781c3 | ||
|
|
46d0e31f5f | ||
|
|
a0b29f42df | ||
|
|
96b3bd1637 | ||
|
|
fc09fa36b8 | ||
|
|
68ea8da6e3 | ||
|
|
052c4fedca | ||
|
|
ad777c695b | ||
|
|
e6df89c98a | ||
|
|
fef4451f4e | ||
|
|
c68f86eeb7 | ||
|
|
fafc2aa1f7 | ||
|
|
ca49415ef6 | ||
|
|
2600b8cc79 | ||
| e1f25a63bc | |||
|
|
c295c06565 | ||
|
|
b146295996 | ||
|
|
41f77bd373 | ||
|
|
d4a8cbc637 | ||
|
|
2300bc4ab6 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,4 +17,6 @@
|
||||
android/build/
|
||||
ios/XCBuildData/
|
||||
env/.env
|
||||
.env
|
||||
.env
|
||||
android/key.properties
|
||||
app/upload-keystore.jks
|
||||
@@ -54,6 +54,15 @@ cp env.example .env
|
||||
- `GOOGLE_CHECK_URL` - URL для проверки соединения (Google)
|
||||
- `YANDEX_CHECK_URL` - URL для проверки соединения (Yandex)
|
||||
|
||||
### VK ID Configuration
|
||||
- `VK_CLIENT_ID` - ID приложения VK (числовой)
|
||||
- **Примечание**: VK_CLIENT_ID - это числовой "ID приложения" из консоли VK ID, НЕ "Защищённый ключ" или "Сервисный ключ доступа"
|
||||
|
||||
### Yandex ID Configuration
|
||||
- `YANDEX_CLIENT_ID` - ID приложения Yandex OAuth
|
||||
- `YANDEX_CLIENT_SECRET` - секретный ключ приложения Yandex OAuth
|
||||
- `YANDEX_REDIRECT_URI` - URI для перенаправления (должен совпадать с настройками в Yandex OAuth приложении)
|
||||
|
||||
## Безопасность
|
||||
|
||||
⚠️ **ВАЖНО**: Файл `.env` добавлен в `.gitignore` и не должен попадать в репозиторий. Все секретные данные должны храниться только в этом файле.
|
||||
|
||||
1
android/app/agconnect-services.json
Normal file
1
android/app/agconnect-services.json
Normal file
@@ -0,0 +1 @@
|
||||
{"agcgw":{"url":"connect-drru.hispace.dbankcloud.ru","backurl":"connect-drru.hispace.dbankcloud.cn","websocketurl":"connect-ws-drru.hispace.dbankcloud.ru","websocketbackurl":"connect-ws-drru.hispace.dbankcloud.cn"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"20007000036693451","product_id":"461323198429446894","client_id":"1595824543740483264","client_secret":"2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE","project_id":"461323198429446894","app_id":"113238569","api_key":"DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==","package_name":"com.eatvioapp.app"},"oauth_client":{"client_id":"113238569","client_type":1},"app_info":{"app_id":"113238569","package_name":"com.eatvioapp.app"},"service":{"analytics":{"collector_url":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drru.cloud.huawei.ru"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"RU","configuration_version":"3.0","appInfos":[{"package_name":"com.eatvioapp.app","client":{"app_id":"113238569"},"app_info":{"package_name":"com.eatvioapp.app","app_id":"113238569"},"oauth_client":{"client_type":1,"client_id":"113238569"}}]}
|
||||
@@ -5,6 +5,7 @@ plugins {
|
||||
// END: FlutterFire Configuration
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
id 'com.huawei.agconnect'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
@@ -13,6 +14,8 @@ if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
def yandexClientId = project.hasProperty('YANDEX_CLIENT_ID') ? project.property('YANDEX_CLIENT_ID') : (System.getenv('YANDEX_CLIENT_ID') ?: "")
|
||||
|
||||
android {
|
||||
namespace = "com.eatvioapp.app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -33,6 +36,10 @@ android {
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
manifestPlaceholders = [
|
||||
YANDEX_CLIENT_ID: yandexClientId,
|
||||
applicationName: "io.flutter.app.FlutterApplication"
|
||||
]
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -47,6 +54,9 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
@@ -57,6 +67,15 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.huawei.agconnect:agconnect-core:1.9.1.301'
|
||||
implementation 'com.huawei.hms:push:6.12.0.300'
|
||||
// Huawei Analytics dependency to satisfy com.huawei.hianalytics.* references during R8
|
||||
implementation 'com.huawei.hms:hianalytics:6.12.0.300'
|
||||
// BouncyCastle provider required by Huawei secure utils used transitively by HMS
|
||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1'
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
-keep class com.huawei.** { *; }
|
||||
-dontwarn com.huawei.**
|
||||
|
||||
-keep class org.bouncycastle.** { *; }
|
||||
-dontwarn org.bouncycastle.**
|
||||
|
||||
-keep class io.flutter.** { *; }
|
||||
-dontwarn io.flutter.**
|
||||
|
||||
-keep class kotlinx.coroutines.** { *; }
|
||||
-dontwarn kotlinx.coroutines.**
|
||||
|
||||
-keep class **.R$* { *; }
|
||||
-keepclassmembers class ** { @androidx.annotation.Keep *; }
|
||||
-keep @androidx.annotation.Keep class * { *; }
|
||||
|
||||
-keep public class * extends android.app.Application
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
|
||||
-keep class * implements android.os.Parcelable { *; }
|
||||
-keepclassmembers enum * { *; }
|
||||
@@ -11,6 +11,16 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<service
|
||||
android:name=".HmsPushService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<meta-data
|
||||
android:name="push_kit_auto_init_enabled"
|
||||
android:value="true" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eatvioapp.app
|
||||
|
||||
import android.util.Log
|
||||
import com.huawei.hms.push.HmsMessageService
|
||||
import com.huawei.hms.push.RemoteMessage
|
||||
|
||||
class HmsPushService : HmsMessageService() {
|
||||
override fun onNewToken(token: String?) {
|
||||
super.onNewToken(token)
|
||||
Log.i("HMS", "onNewToken: ${token ?: "null"}")
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage?) {
|
||||
super.onMessageReceived(message)
|
||||
Log.i("HMS", "onMessageReceived: ${message?.data}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,71 @@
|
||||
package com.eatvioapp.app
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import com.huawei.agconnect.config.AGConnectServicesConfig
|
||||
import com.huawei.hms.aaid.HmsInstanceId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val channelName = "com.eatvioapp/hms_push"
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"getHuaweiToken" -> {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
// Try to read appId from agconnect-services.json
|
||||
var appId: String? = try {
|
||||
AGConnectServicesConfig.fromContext(applicationContext)
|
||||
.getString("client/app_id")
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
// Fallback to manifest meta-data if needed
|
||||
if (appId.isNullOrBlank()) {
|
||||
try {
|
||||
val pm: PackageManager = applicationContext.packageManager
|
||||
val ai: ApplicationInfo = pm.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
|
||||
val raw = ai.metaData?.getString("com.huawei.hms.client.appid")
|
||||
if (!raw.isNullOrBlank()) {
|
||||
appId = raw.removePrefix("appid=")
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// ignore and handle below
|
||||
}
|
||||
}
|
||||
|
||||
if (appId.isNullOrBlank()) {
|
||||
runOnUiThread {
|
||||
result.error("HMS_TOKEN_ERROR", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.", null)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
val token = HmsInstanceId.getInstance(applicationContext)
|
||||
.getToken(appId, "HCM")
|
||||
runOnUiThread {
|
||||
result.success(token)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
result.error("HMS_TOKEN_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {url 'https://developer.huawei.com/repo/'}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.12.0'
|
||||
classpath 'com.huawei.agconnect:agcp:1.9.1.301'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {url 'https://developer.huawei.com/repo/'}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +27,32 @@ subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
// Temporary safeguard: set missing namespaces for Android library modules based on their manifest package
|
||||
subprojects { Project subproject ->
|
||||
subproject.pluginManager.withPlugin('com.android.library') {
|
||||
def androidExt = subproject.extensions.findByName('android')
|
||||
if (androidExt != null) {
|
||||
def hasNamespaceProp = androidExt.hasProperty('namespace')
|
||||
def currentNamespace = hasNamespaceProp ? androidExt.namespace : null
|
||||
if (hasNamespaceProp && !currentNamespace) {
|
||||
def manifestFile = file("${subproject.projectDir}/src/main/AndroidManifest.xml")
|
||||
if (manifestFile.exists()) {
|
||||
try {
|
||||
def manifest = new groovy.xml.XmlSlurper().parse(manifestFile)
|
||||
def manifestPackage = manifest.@package?.text()
|
||||
if (manifestPackage) {
|
||||
androidExt.namespace = manifestPackage
|
||||
subproject.logger.lifecycle("[namespace-fix] Set namespace for ${subproject.path} to '${manifestPackage}' from AndroidManifest.xml")
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
subproject.logger.warn("[namespace-fix] Failed to read manifest for ${subproject.path}: ${t.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,6 +12,7 @@ pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://developer.huawei.com/repo/' }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
1
assets/icons/consultant_rating_icon.svg
Normal file
1
assets/icons/consultant_rating_icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2F80ED"><path d="M759.23-353.08q8.92-8.92 21.19-8.92 12.27 0 21.19 8.92l63.39 64q8.92 9.07 8.92 21.15 0 12.08-8.92 21.01-8.92 8.92-21.01 8.92-12.08 0-21.14-8.92l-64-63.39q-8.93-8.92-8.73-21.38.19-12.46 9.11-21.39Zm33.85-472.22q8.92 9 8.92 21.38 0 12.38-8.92 21.3l-63.39 63.39q-8.92 8.92-21.19 8.92-12.27 0-21.19-9-8.92-9.01-8.92-21.39t8.92-21.3l63.61-63.38q9.07-8.93 21.15-8.93 12.08 0 21.01 9.01ZM167-825q9.01-8.92 21.39-8.92t21.3 8.92l63.39 63.62q8.92 9.06 8.92 21.14 0 12.09-9 21.01-9.01 8.92-21.39 8.92t-21.3-8.92l-63.39-63.39q-8.92-8.92-8.92-21.19 0-12.27 9-21.19Zm33.77 472.31q8.92 8.92 8.92 21.19 0 12.27-8.92 21.19l-63.39 63.39q-8.92 8.92-21.19 8.92-12.27 0-21.19-9-8.92-9.01-8.92-21.39t8.92-21.3l63.62-63.39q8.92-8.92 21.07-8.73 12.16.2 21.08 9.12Zm135.54 86.85L480-352.23l144.08 87.77-37.62-163.62 126.39-109.07-166.39-14.93L480-706.54l-66.08 153.46-166.38 14.54 126.38 110.46-37.61 162.24ZM480.38-486ZM480-281.31 306.69-177.08q-8.69 5.08-17.42 4.27-8.73-.81-15.81-5.88-7.07-5.08-10.92-13.27-3.85-8.19-1.23-18.12L307.23-406 154.08-538.39q-7.69-6.83-9.62-15.57-1.92-8.73 1.31-17.04 3.23-8.31 9.28-13.62 6.05-5.32 16.64-6.84l201.7-17.39 78.46-185.3q3.84-9.31 11.65-13.77 7.81-4.46 16.5-4.46 8.69 0 16.5 4.46 7.81 4.46 11.65 13.77L587-608.85l201.69 17.39q10.68 1.51 16.78 6.82 6.1 5.3 9.14 13.64 3.24 8.31 1.31 17.04-1.92 8.74-9.61 15.57L653.15-406l45.93 196.31q2.61 9.92-1.23 17.92-3.85 8-10.93 13.08-7.07 5.07-15.8 5.88-8.73.81-17.43-4.27L480-281.31Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
11
assets/icons/mail_logo.svg
Normal file
11
assets/icons/mail_logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1713_218)">
|
||||
<path d="M7.48034 0.0116882H20.547C24.6707 0.0116882 28.0137 3.35463 28.0137 7.47836V20.545C28.0137 24.6687 24.6707 28.0117 20.547 28.0117H7.48034C3.35661 28.0117 0.0136719 24.6687 0.0136719 20.545V7.47836C0.0136717 3.35463 3.35661 0.0116882 7.48034 0.0116882Z" fill="#0077FF"/>
|
||||
<path d="M14.0133 3.43393C19.8423 3.43393 24.5911 8.18276 24.5911 14.0117C24.5911 17.6432 22.8964 19.3565 20.7548 19.3565C19.5071 19.3565 18.4269 18.7047 17.8124 17.7363C16.844 18.7233 15.5031 19.3378 14.0133 19.3378C11.0709 19.3378 8.6872 16.9541 8.6872 14.0117C8.6872 11.0693 11.0709 8.68558 14.0133 8.68558C16.9557 8.68558 19.3395 11.0693 19.3395 14.0117V15.7436C19.3395 16.6189 19.954 17.2334 20.7548 17.2334C21.7046 17.2334 22.4867 16.4327 22.4867 14.0117C22.4867 9.33739 18.6876 5.53832 14.0133 5.53832C9.339 5.53832 5.53993 9.33739 5.53993 14.0117C5.53993 18.686 9.339 22.4851 14.0133 22.4851C16.7136 22.4851 18.5759 21.1443 18.5759 21.1443L19.9726 22.7458C19.9726 22.7458 17.5889 24.5895 14.0133 24.5895C8.18438 24.5895 3.43555 19.8407 3.43555 14.0117C3.43555 8.18276 8.18438 3.43393 14.0133 3.43393ZM10.7916 14.0117C10.7916 15.7809 12.2255 17.2334 14.0133 17.2334C15.7825 17.2334 17.2351 15.7809 17.2351 14.0117C17.2351 12.2239 15.7825 10.79 14.0133 10.79C12.2255 10.79 10.7916 12.2239 10.7916 14.0117Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1713_218">
|
||||
<rect width="28" height="28" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
5
assets/icons/ok_logo.svg
Normal file
5
assets/icons/ok_logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 13.4393C0 7.10718 0 3.92462 1.9623 1.96231C3.9411 1.57261e-07 7.10718 0 13.4393 0H14.5607C20.8928 0 24.0754 1.57261e-07 26.0377 1.96231C28 3.92462 28 7.10718 28 13.4393V14.5607C28 20.8928 28 24.0754 26.0377 26.0377C24.0754 28 20.8928 28 14.5607 28H13.4393C7.10718 28 3.92461 28 1.9623 26.0377C0 24.0754 0 20.9093 0 14.5607V13.4393Z" fill="#FF7700"/>
|
||||
<path d="M13.9997 13.934C15.3684 13.934 16.6051 13.3734 17.4956 12.4829C18.3861 11.5925 18.9467 10.3557 18.9467 8.98704C18.9467 7.61837 18.3861 6.38162 17.4956 5.49116C16.6051 4.6007 15.3684 4.04004 13.9997 4.04004C12.631 4.04004 11.3943 4.6007 10.5039 5.49116C9.61339 6.38162 9.05273 7.61837 9.05273 8.98704C9.05273 10.3557 9.61339 11.5925 10.5039 12.4829C11.3943 13.3734 12.6475 13.934 13.9997 13.934ZM12.4167 7.38751C12.8289 6.97526 13.3896 6.72791 13.9997 6.72791C14.6263 6.72791 15.187 6.97526 15.5828 7.38751C15.995 7.79976 16.2424 8.36042 16.2424 8.97055C16.2424 9.59716 15.995 10.1578 15.5828 10.5536C15.1705 10.9658 14.6098 11.2132 13.9997 11.2132C13.3731 11.2132 12.8124 10.9658 12.4167 10.5536C12.0044 10.1413 11.7571 9.58067 11.7571 8.97055C11.7571 8.36042 12.0209 7.79976 12.4167 7.38751Z" fill="white"/>
|
||||
<path d="M20.6615 15.9293L19.2104 13.9505C19.1279 13.8351 18.963 13.8186 18.8641 13.9175C17.4954 15.0883 15.8134 15.8469 13.9995 15.8469C12.1856 15.8469 10.5037 15.0883 9.13498 13.9175C9.03604 13.8351 8.87114 13.8516 8.78869 13.9505L7.33758 15.9293C7.27162 16.0283 7.28811 16.1602 7.37056 16.2426C8.59081 17.232 10.009 17.9081 11.4931 18.2544L8.42591 23.6631C8.34346 23.8115 8.45889 24.0094 8.62379 24.0094H11.625C11.7239 24.0094 11.8064 23.9599 11.8393 23.861L13.983 19.0789L16.1267 23.861C16.1597 23.9434 16.2422 24.0094 16.3411 24.0094H19.3423C19.5237 24.0094 19.6226 23.828 19.5402 23.6631L16.473 18.2544C17.9571 17.9081 19.3753 17.2485 20.5955 16.2426C20.7274 16.1602 20.7439 16.0283 20.6615 15.9293Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
16
assets/icons/vk_logo.svg
Normal file
16
assets/icons/vk_logo.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1709_298)">
|
||||
<mask id="mask0_1709_298" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="28">
|
||||
<path d="M27.8614 0H0.138672V28H27.8614V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1709_298)">
|
||||
<path d="M0.138672 13.44C0.138672 7.10433 0.138672 3.93649 2.08743 1.96824C4.03619 0 7.17266 0 13.4456 0H14.5545C20.8275 0 23.9639 0 25.9127 1.96824C27.8614 3.93649 27.8614 7.10433 27.8614 13.44V14.56C27.8614 20.8957 27.8614 24.0635 25.9127 26.0318C23.9639 28 20.8275 28 14.5545 28H13.4456C7.17266 28 4.03619 28 2.08743 26.0318C0.138672 24.0635 0.138672 20.8957 0.138672 14.56V13.44Z" fill="#0077FF"/>
|
||||
<path d="M14.89 20.1718C8.57149 20.1718 4.96756 15.7968 4.81738 8.51675H7.98241C8.08637 13.8601 10.4197 16.1234 12.2678 16.5901V8.51675H15.2481V13.125C17.0732 12.9267 18.9905 10.8268 19.6373 8.51675H22.6176C22.1209 11.3634 20.0417 13.4634 18.5632 14.3267C20.0417 15.0267 22.4098 16.8584 23.3108 20.1718H20.0302C19.3255 17.9551 17.5699 16.2401 15.2481 16.0067V20.1718H14.89Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1709_298">
|
||||
<rect width="28" height="28" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
16
assets/icons/yandex_logo.svg
Normal file
16
assets/icons/yandex_logo.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1709_319)">
|
||||
<mask id="mask0_1709_319" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="28">
|
||||
<path d="M27.8614 0H0.138672V28H27.8614V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1709_319)">
|
||||
<path d="M0.138672 13.44C0.138672 7.10433 0.138672 3.93649 2.08743 1.96824C4.03619 0 7.17266 0 13.4456 0H14.5545C20.8275 0 23.9639 0 25.9127 1.96824C27.8614 3.93649 27.8614 7.10433 27.8614 13.44V14.56C27.8614 20.8957 27.8614 24.0635 25.9127 26.0318C23.9639 28 20.8275 28 14.5545 28H13.4456C7.17266 28 4.03619 28 2.08743 26.0318C0.138672 24.0635 0.138672 20.8957 0.138672 14.56V13.44Z" fill="#FC3F1D"/>
|
||||
</g>
|
||||
<path d="M17.3371 24H21V4H15.6687C10.3069 4 7.49454 6.614 7.49454 10.4755C7.49454 13.809 9.31785 15.6885 12.5476 17.73L14.51 18.9635L11.9911 16.9515L7 24H10.971L16.3029 16.423L14.4486 15.248C12.2079 13.809 11.1107 12.693 11.1107 10.27C11.1107 8.141 12.6873 6.702 15.6844 6.702H17.3224V24H17.3382H17.3371Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1709_319">
|
||||
<rect width="28" height="28" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
37
integration_test/auth_screen/auth_login_test.dart
Normal file
37
integration_test/auth_screen/auth_login_test.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/main.dart' as app;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import '../common_cases/skip_welcome_screen.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async {
|
||||
await AppInitializer.initialize(buildType: AppBuildType.tests);
|
||||
});
|
||||
|
||||
testWidgets('Signs in successfully with valid credentials', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const app.MyApp());
|
||||
await tester.pumpAndSettle(const Duration(seconds: 3));
|
||||
await skipWelcomeScreen(tester);
|
||||
final emailFieldFinder = find.byKey(AppKeys.emailTextFieldAuthScreen);
|
||||
final passwordFieldFinder = find.byKey(AppKeys.passwordTextFieldAuthScreen);
|
||||
final signInButtonFinder = find.byKey(AppKeys.signInButtonAuthScreen);
|
||||
|
||||
expect(emailFieldFinder, findsOneWidget, reason: 'Email text field should be found.');
|
||||
expect(passwordFieldFinder, findsOneWidget, reason: 'Password text field should be found.');
|
||||
expect(signInButtonFinder, findsOneWidget, reason: 'Sign In button should be found.');
|
||||
|
||||
await tester.enterText(emailFieldFinder, 'reshetnick.mike+user4@gmail.com');
|
||||
await tester.enterText(passwordFieldFinder, '1234567890');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(signInButtonFinder);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 3));
|
||||
|
||||
expect(signInButtonFinder, findsNothing, reason: 'Sign-in button should not be visible after successful login and navigation.');
|
||||
});
|
||||
}
|
||||
31
integration_test/auth_screen/auth_logo_button_test.dart
Normal file
31
integration_test/auth_screen/auth_logo_button_test.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:eatvio_app/main.dart' as app;
|
||||
import '../common_cases/skip_welcome_screen.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async {
|
||||
await AppInitializer.initialize(buildType: AppBuildType.tests);
|
||||
});
|
||||
|
||||
testWidgets('tapping logo 7 times opens dev tools', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const app.MyApp());
|
||||
await tester.pumpAndSettle(const Duration(seconds: 3));
|
||||
await skipWelcomeScreen(tester);
|
||||
final logoButtonFinder = find.byKey(AppKeys.hiddenLogoButtonAuthScreen);
|
||||
expect(logoButtonFinder, findsOneWidget, reason: 'The logo GestureDetector should be found in auth screen');
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
await tester.tap(logoButtonFinder);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(DevToolsScreen), findsOneWidget, reason: 'DevToolsScreen should be visible after tapping the logo 7 times.');
|
||||
});
|
||||
}
|
||||
8
integration_test/common_cases/skip_welcome_screen.dart
Normal file
8
integration_test/common_cases/skip_welcome_screen.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Future<void> skipWelcomeScreen(WidgetTester tester) async {
|
||||
final welcomeScreenButtonFinder = find.byKey(AppKeys.welcomeScreenButton);
|
||||
await tester.tap(welcomeScreenButtonFinder);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
@@ -388,7 +388,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -539,7 +539,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -578,7 +578,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import UIKit
|
||||
import YandexLoginSDK
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@@ -10,4 +11,20 @@ import Flutter
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
@available(iOS 8.0, *)
|
||||
func application(
|
||||
_ application: UIApplication, continue userActivity: NSUserActivity,
|
||||
restorationHandler: @escaping ([Any]?) -> Void
|
||||
) -> Bool {
|
||||
YXLSdk.shared.processUserActivity(userActivity)
|
||||
return true
|
||||
}
|
||||
|
||||
override public func application(
|
||||
_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
||||
) -> Bool {
|
||||
return YXLSdk.shared.handleOpen(
|
||||
url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>72</string>
|
||||
<string>74</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
@@ -67,5 +67,24 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>yandexauth</string>
|
||||
<string>yandexauth2</string>
|
||||
<string>yandexauth4</string>
|
||||
</array>
|
||||
<key>YAClientId</key>
|
||||
<string>5399b03636104c6d920b87c1ad329254</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>YandexLoginSDK</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>yx5399b03636104c6d920b87c1ad329254</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<array>
|
||||
<string>applinks:eatvio.ru</string>
|
||||
<string>applinks:www.eatvio.ru</string>
|
||||
<string>applinks:5399b03636104c6d920b87c1ad329254.oauth.yandex.ru</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
13
lib/app_keys.dart
Normal file
13
lib/app_keys.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppKeys {
|
||||
|
||||
// Auth Screen
|
||||
static const hiddenLogoButtonAuthScreen = ValueKey('hiddenLogoButtonAuthScreen');
|
||||
static const emailTextFieldAuthScreen = ValueKey('emailTextFieldAuthScreen');
|
||||
static const passwordTextFieldAuthScreen = ValueKey('passwordTextFieldAuthScreen');
|
||||
static const signInButtonAuthScreen = ValueKey('signInButtonAuthScreen');
|
||||
|
||||
// Welcome Screen
|
||||
static const welcomeScreenButton = ValueKey('welcomeScreenButton');
|
||||
}
|
||||
55
lib/core/app_config.dart
Normal file
55
lib/core/app_config.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
/// Конфигурация приложения
|
||||
class AppConfig {
|
||||
AppConfig({required this.buildType}){
|
||||
_baseUrl = switch (buildType) {
|
||||
AppBuildType.prod => EnvConfig.baseUrl,
|
||||
AppBuildType.tests => EnvConfig.devBaseUrl,
|
||||
AppBuildType.dev => EnvConfig.devBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
final AppBuildType buildType;
|
||||
late final String _baseUrl;
|
||||
String get baseUrl => _baseUrl;
|
||||
|
||||
void setBaseUrl(String baseUrls) {
|
||||
_baseUrl = baseUrls;
|
||||
}
|
||||
|
||||
/// Список глобальных BlocProvider'ов
|
||||
List<BlocProvider> get globalBlocProviders => [
|
||||
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
|
||||
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()..getNotifications()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
|
||||
BlocProvider<ChatsCubit>(create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading()),
|
||||
BlocProvider<ViewsCubit>(create: (context) => sl<ViewsCubit>()),
|
||||
BlocProvider<InternetConnectionCubit>(lazy: false, create: (context) => sl<InternetConnectionCubit>()),
|
||||
];
|
||||
|
||||
/// Делегаты локализации
|
||||
List<LocalizationsDelegate> get localizationDelegates => [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// Поддерживаемые локали
|
||||
List<Locale> get supportedLocales => [const Locale.fromSubtags(languageCode: 'ru')];
|
||||
}
|
||||
74
lib/core/app_initializer.dart
Normal file
74
lib/core/app_initializer.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'dart:io' as io;
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart' as di;
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/firebase_options.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
/// Класс для инициализации приложения
|
||||
class AppInitializer {
|
||||
/// Инициализация всех сервисов приложения
|
||||
static Future<void> initialize({required AppBuildType buildType}) async {
|
||||
await _initializeEnvironment();
|
||||
await _initializeDependencyInjection(buildType: buildType);
|
||||
await _initializeSentry();
|
||||
await _initializeFirebase();
|
||||
await _initializeDebugMode();
|
||||
await _initializeAnalytics();
|
||||
}
|
||||
|
||||
/// Инициализация Sentry
|
||||
static Future<void> _initializeSentry() async {
|
||||
final widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
}
|
||||
|
||||
/// Загрузка переменных окружения
|
||||
static Future<void> _initializeEnvironment() async {
|
||||
await dotenv.load(fileName: ".env");
|
||||
}
|
||||
|
||||
/// Инициализация Firebase
|
||||
static Future<void> _initializeFirebase() async {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
if (sl<AppConfig>().buildType == AppBuildType.prod) {
|
||||
await FirebaseMessaging.instance.requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
/// Инициализация системы внедрения зависимостей
|
||||
static Future<void> _initializeDependencyInjection({required AppBuildType buildType}) async {
|
||||
await di.init(buildType: buildType);
|
||||
}
|
||||
|
||||
/// Настройка режима отладки
|
||||
static Future<void> _initializeDebugMode() async {
|
||||
if (kDebugMode) {
|
||||
io.HttpClient.enableTimelineLogging = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Инициализация аналитики
|
||||
static Future<void> _initializeAnalytics() async {
|
||||
AppMetrica.activate(AppMetricaConfig(EnvConfig.appMetricaApiKey));
|
||||
AppMetrica.reportEvent('Application started');
|
||||
}
|
||||
|
||||
/// Запуск приложения с Sentry
|
||||
static Future<void> runAppWithSentry(VoidCallback appRunner) async {
|
||||
await SentryFlutter.init((options) {
|
||||
options.dsn = EnvConfig.sentryDsn;
|
||||
options.tracesSampleRate = 1.0;
|
||||
options.debug = false;
|
||||
}, appRunner: appRunner);
|
||||
}
|
||||
}
|
||||
|
||||
enum AppBuildType { prod, tests, dev }
|
||||
@@ -1,455 +1,58 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||
import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/change_password_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/general_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/other_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_activity_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
|
||||
import 'package:eatvio_app/features/support/presentation/pages/support_screen.dart';
|
||||
import 'package:eatvio_app/features/update_screen/presentation/update_screen.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.dart';
|
||||
import 'package:eatvio_app/core/routing/auth_routes.dart';
|
||||
import 'package:eatvio_app/core/routing/content_routes.dart';
|
||||
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
|
||||
import 'package:eatvio_app/core/routing/other_routes.dart';
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/core/routing/settings_routes.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'dart:developer';
|
||||
|
||||
/// Главный роутер приложения
|
||||
class AppRouter {
|
||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
static GoRouter appRouter = GoRouter(
|
||||
initialLocation: '/update-screen',
|
||||
initialLocation: RouteConstants.updateScreen,
|
||||
navigatorKey: navigatorKey,
|
||||
redirect: (context, state) {
|
||||
final segments = state.uri.pathSegments;
|
||||
if (segments.length >= 3) {
|
||||
final type = segments[1];
|
||||
final contentIdentifier = segments[2];
|
||||
|
||||
switch (type) {
|
||||
case 'recipe':
|
||||
FlutterNativeSplash.remove();
|
||||
return '/about-recipes-screen?slug=$contentIdentifier';
|
||||
case 'blog_post':
|
||||
FlutterNativeSplash.remove();
|
||||
return '/blog-post-screen?id=$contentIdentifier';
|
||||
case 'plan':
|
||||
FlutterNativeSplash.remove();
|
||||
return '/about-plan-screen?slug=$contentIdentifier';
|
||||
case 'diet':
|
||||
FlutterNativeSplash.remove();
|
||||
return '/about-diet-screen?slug=$contentIdentifier';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
redirect: _handleRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/auth-screen',
|
||||
name: 'authScreen',
|
||||
builder: (context, state) => const AuthScreen(),
|
||||
routes: [
|
||||
GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||
GoRoute(
|
||||
path: 'forget-password-screen',
|
||||
name: 'forgetPasswordScreen',
|
||||
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
|
||||
),
|
||||
GoRoute(path: 'scan-qr-code-screen', name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) {
|
||||
return BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return BottomNavigationBarWidget(profileModel: state.profile, child: child);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/my-profile-screen',
|
||||
name: 'myProfileScreen',
|
||||
pageBuilder: (context, state) {
|
||||
return const NoTransitionPage(child: ProfileScreen());
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/feed-screen',
|
||||
name: 'feedScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/diary-screen',
|
||||
name: 'diaryScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultationsScreen.path,
|
||||
name: ConsultationsScreen.name,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-select-screen',
|
||||
name: 'chatSelectScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/notifications-screen',
|
||||
name: 'notificationsScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/menu-screen',
|
||||
name: 'menuScreen',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return MenuScreen(profileModel: state.profile);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(name: 'blockedUsersScreen', path: '/blocked-users-screen', builder: (context, state) => const BlockedUsersScreen()),
|
||||
GoRoute(
|
||||
path: '/welcome-screen',
|
||||
name: 'welcomeScreen',
|
||||
builder: (context, state) => const WelcomeScreen(),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
if (welcomeScreenWasShowed == false) {
|
||||
return null;
|
||||
} else if (await TokenStorage.haveTokenInStorage()) {
|
||||
return '/feed-screen';
|
||||
} else {
|
||||
return '/auth-screen';
|
||||
}
|
||||
} catch (e) {
|
||||
return '/feed-screen';
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/update-screen',
|
||||
name: 'updateScreen',
|
||||
builder: (context, state) => const UpdateScreen(),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
String version = packageInfo.version;
|
||||
Version currentVersion = Version.parse(version);
|
||||
Version latestVersion = Version.parse(versions['min_version']);
|
||||
if (latestVersion > currentVersion) {
|
||||
return null;
|
||||
} else {
|
||||
return '/welcome-screen';
|
||||
}
|
||||
} catch (e) {
|
||||
return '/welcome-screen';
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
|
||||
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
|
||||
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
|
||||
GoRoute(path: '/user-finance-screen', name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
|
||||
GoRoute(
|
||||
path: CardSettingsScreen.path,
|
||||
name: CardSettingsScreen.name,
|
||||
builder: (context, state) {
|
||||
final chosenPlanId = state.extra as int?;
|
||||
return CardSettingsScreen(chosenPlanId: chosenPlanId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/about-plan-screen',
|
||||
name: 'aboutPlanScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
|
||||
GoRoute(
|
||||
path: '/about-recipes-screen',
|
||||
name: 'aboutRecipesScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
name: RecipeCreateScreen.name,
|
||||
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile-screen',
|
||||
name: 'profileScreen',
|
||||
builder: (context, state) => ProfileScreen(author: state.extra as Author),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/subs-screen/:slug',
|
||||
name: 'subsScreen',
|
||||
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/about-diet-screen',
|
||||
name: 'aboutDietScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
|
||||
}
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-create-screen',
|
||||
name: 'blogPostCreateScreen',
|
||||
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-screen',
|
||||
name: 'blogPostScreen',
|
||||
builder: (context, state) {
|
||||
final id = state.uri.queryParameters['id'];
|
||||
final parsedId = int.tryParse(id ?? '');
|
||||
if (parsedId != null) {
|
||||
return BlogPostScreen(id: parsedId);
|
||||
}
|
||||
|
||||
return BlogPostScreen(id: state.extra as int);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final uuid = data['uuid'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/empty-chat-screen',
|
||||
name: 'emptyChatScreen',
|
||||
builder: (context, state) => EmptyChatScreen(author: state.extra as Author),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings-screen',
|
||||
name: 'settingsScreen',
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'general-info-screen',
|
||||
name: 'generalInfoScreen',
|
||||
builder: (context, state) => GeneralInfoScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'other-info-screen',
|
||||
name: 'otherInfoScreen',
|
||||
builder: (context, state) => OtherInfoScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'physical-activity-screen',
|
||||
name: 'physicalActivityScreen',
|
||||
builder: (context, state) => PhysicalActivityScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'physical-form-screen',
|
||||
name: 'physicalFormScreen',
|
||||
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'pressure-screen',
|
||||
name: 'pressureScreen',
|
||||
builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'change-password-screen',
|
||||
name: 'changePasswordScreen',
|
||||
builder: (context, state) => ChangePasswordScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'change-email-screen',
|
||||
name: 'changeEmailScreen',
|
||||
builder: (context, state) => ChangeEmailScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: '/become-consultant-screen', name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
||||
GoRoute(
|
||||
path: '/stepper-become-consultant-screen',
|
||||
name: 'stepperBecomeConsultantScreen',
|
||||
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
|
||||
),
|
||||
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||
GoRoute(
|
||||
path: '/photo-view-screen/:index',
|
||||
name: 'photoViewScreen',
|
||||
builder: (context, state) => PhotoViewScreen(imageUrls: state.extra as List<String>, index: int.parse(state.pathParameters['index']!)),
|
||||
),
|
||||
GoRoute(
|
||||
path: CreateDietScreen.path,
|
||||
name: CreateDietScreen.name,
|
||||
builder: (context, state) {
|
||||
final diet = state.extra as Diet?;
|
||||
return CreateDietScreen(diet: diet);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: DietPreviewScreen.path,
|
||||
name: DietPreviewScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Diet;
|
||||
return DietPreviewScreen(diet: data);
|
||||
},
|
||||
),
|
||||
GoRoute(path: CreatePlanScreen.path, name: CreatePlanScreen.name, builder: (context, state) => const CreatePlanScreen()),
|
||||
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
|
||||
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
|
||||
GoRoute(
|
||||
path: UserDetailsScreen.path,
|
||||
name: UserDetailsScreen.name,
|
||||
builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
|
||||
),
|
||||
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
|
||||
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
|
||||
GoRoute(
|
||||
path: InfoByOrderScreen.path,
|
||||
name: InfoByOrderScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final successfulPayment = data['successfulPayment'] as bool?;
|
||||
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: PaymentScreen.path,
|
||||
name: PaymentScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final url = data['url'] as String;
|
||||
return PaymentScreen(url: url, planId: planId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: UserDiaryScreen.path,
|
||||
name: UserDiaryScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, int>;
|
||||
final diaryId = data['diaryId'] as int;
|
||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
||||
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/verify-screen',
|
||||
name: 'verifyScreen',
|
||||
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
||||
),
|
||||
GoRoute(path: '/verify-email-screen', name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
|
||||
GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
|
||||
GoRoute(path: '/notifications-settings-screen', name: 'notificationsSettingsScreen', builder: (context, state) => const NotificationsSettingsScreen()),
|
||||
...AuthRoutes.createRoutes(),
|
||||
...MainNavigationRoutes.createRoutes(),
|
||||
...ContentRoutes.createRoutes(),
|
||||
...SettingsRoutes.createRoutes(),
|
||||
...OtherRoutes.createRoutes(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Обработка редиректов для deep links
|
||||
static String? _handleRedirect(BuildContext context, GoRouterState state) {
|
||||
final segments = state.uri.pathSegments;
|
||||
if (segments.length >= 3) {
|
||||
final type = segments[1];
|
||||
final contentIdentifier = segments[2];
|
||||
|
||||
switch (type) {
|
||||
case 'recipe':
|
||||
FlutterNativeSplash.remove();
|
||||
return '${RouteConstants.aboutRecipesScreen}?slug=$contentIdentifier';
|
||||
case 'blog_post':
|
||||
FlutterNativeSplash.remove();
|
||||
return '${RouteConstants.blogPostScreen}?id=$contentIdentifier';
|
||||
case 'plan':
|
||||
FlutterNativeSplash.remove();
|
||||
return '${RouteConstants.aboutPlanScreen}?slug=$contentIdentifier';
|
||||
case 'diet':
|
||||
FlutterNativeSplash.remove();
|
||||
return '${RouteConstants.aboutDietScreen}?slug=$contentIdentifier';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
|
||||
appRouter.pushNamed('registerViaSocialScreen', extra: {'authResult': authResult, 'provider': provider});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
|
||||
String baseUrl = EnvConfig.baseUrl;
|
||||
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
|
||||
const buildDate = String.fromEnvironment('BUILD_DATE', defaultValue: '');
|
||||
|
||||
|
||||
72
lib/core/constants/app_constants.dart
Normal file
72
lib/core/constants/app_constants.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
/// Константы приложения
|
||||
class AppConstants {
|
||||
// Размеры
|
||||
static const double defaultPadding = 16.0;
|
||||
static const double smallPadding = 8.0;
|
||||
static const double largePadding = 24.0;
|
||||
static const double extraLargePadding = 32.0;
|
||||
|
||||
// Радиусы
|
||||
static const double defaultBorderRadius = 8.0;
|
||||
static const double smallBorderRadius = 4.0;
|
||||
static const double largeBorderRadius = 16.0;
|
||||
|
||||
// Высоты
|
||||
static const double defaultButtonHeight = 48.0;
|
||||
static const double smallButtonHeight = 36.0;
|
||||
static const double largeButtonHeight = 56.0;
|
||||
|
||||
// Ширины
|
||||
static const double maxContentWidth = 400.0;
|
||||
static const double minContentWidth = 280.0;
|
||||
|
||||
// Анимации
|
||||
static const Duration defaultAnimationDuration = Duration(milliseconds: 300);
|
||||
static const Duration fastAnimationDuration = Duration(milliseconds: 150);
|
||||
static const Duration slowAnimationDuration = Duration(milliseconds: 500);
|
||||
|
||||
// Таймауты
|
||||
static const Duration defaultTimeout = Duration(seconds: 30);
|
||||
static const Duration shortTimeout = Duration(seconds: 10);
|
||||
static const Duration longTimeout = Duration(minutes: 2);
|
||||
|
||||
// Лимиты
|
||||
static const int maxImageSize = 5 * 1024 * 1024; // 5MB
|
||||
static const int maxVideoSize = 100 * 1024 * 1024; // 100MB
|
||||
static const int maxTextLength = 1000;
|
||||
static const int maxTitleLength = 100;
|
||||
static const int maxDescriptionLength = 500;
|
||||
|
||||
// Пагинация
|
||||
static const int defaultPageSize = 20;
|
||||
static const int smallPageSize = 10;
|
||||
static const int largePageSize = 50;
|
||||
|
||||
// Форматы
|
||||
static const List<String> supportedImageFormats = ['jpg', 'jpeg', 'png', 'webp'];
|
||||
static const List<String> supportedVideoFormats = ['mp4', 'mov', 'avi'];
|
||||
|
||||
// Регулярные выражения
|
||||
static const String emailRegex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
|
||||
static const String phoneRegex = r'^\+?[1-9]\d{1,14}$';
|
||||
static const String urlRegex = r'^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$';
|
||||
|
||||
// Сообщения
|
||||
static const String defaultErrorMessage = 'Произошла ошибка. Попробуйте еще раз.';
|
||||
static const String networkErrorMessage = 'Проблема с подключением к интернету.';
|
||||
static const String timeoutErrorMessage = 'Превышено время ожидания.';
|
||||
static const String unknownErrorMessage = 'Неизвестная ошибка.';
|
||||
|
||||
// Ключи для SharedPreferences
|
||||
static const String welcomeScreenShownKey = 'welcomeScreenWasShowed';
|
||||
static const String userTokenKey = 'userToken';
|
||||
static const String userDataKey = 'userData';
|
||||
static const String settingsKey = 'settings';
|
||||
|
||||
// API endpoints
|
||||
static const String apiVersion = '/api';
|
||||
static const String authEndpoint = '/auth';
|
||||
static const String usersEndpoint = '/users';
|
||||
static const String postsEndpoint = '/posts';
|
||||
static const String commentsEndpoint = '/comments';
|
||||
}
|
||||
42
lib/core/cubit/base_cubit.dart
Normal file
42
lib/core/cubit/base_cubit.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:developer';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
/// Базовый класс для всех Cubit'ов с унифицированной обработкой ошибок
|
||||
abstract class BaseCubit<State> extends Cubit<State> {
|
||||
BaseCubit(super.initialState);
|
||||
|
||||
/// Унифицированная обработка ошибок
|
||||
void handleError(Object error, [StackTrace? stackTrace]) {
|
||||
log('Error in $runtimeType: $error', stackTrace: stackTrace);
|
||||
// Здесь можно добавить отправку ошибок в Sentry или другую систему мониторинга
|
||||
}
|
||||
|
||||
/// Безопасное выполнение асинхронных операций
|
||||
Future<T?> safeExecute<T>(Future<T> Function() operation) async {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error, stackTrace) {
|
||||
handleError(error, stackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Безопасное выполнение асинхронных операций с обработкой состояния
|
||||
Future<T?> safeExecuteWithState<T>(
|
||||
Future<T> Function() operation, {
|
||||
required State loadingState,
|
||||
required State Function(T result) successState,
|
||||
required State Function(Object error) errorState,
|
||||
}) async {
|
||||
try {
|
||||
emit(loadingState);
|
||||
final result = await operation();
|
||||
emit(successState(result));
|
||||
return result;
|
||||
} catch (error, stackTrace) {
|
||||
handleError(error, stackTrace);
|
||||
emit(errorState(error));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
lib/core/cubit/state_utils.dart
Normal file
28
lib/core/cubit/state_utils.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Утилиты для работы с состояниями Cubit'ов
|
||||
class StateUtils {
|
||||
/// Проверяет, является ли состояние загрузочным
|
||||
static bool isLoading(dynamic state) {
|
||||
return state.toString().contains('loading') || state.toString().contains('Loading');
|
||||
}
|
||||
|
||||
/// Проверяет, является ли состояние загруженным
|
||||
static bool isLoaded(dynamic state) {
|
||||
return state.toString().contains('loaded') || state.toString().contains('Loaded');
|
||||
}
|
||||
|
||||
/// Проверяет, является ли состояние ошибочным
|
||||
static bool isError(dynamic state) {
|
||||
return state.toString().contains('error') || state.toString().contains('Error');
|
||||
}
|
||||
|
||||
/// Получает сообщение об ошибке из состояния
|
||||
static String? getErrorMessage(dynamic state) {
|
||||
if (isError(state)) {
|
||||
// Попытка извлечь сообщение об ошибке из состояния
|
||||
final stateString = state.toString();
|
||||
final errorMatch = RegExp(r'error[:\s]+([^,}]+)').firstMatch(stateString);
|
||||
return errorMatch?.group(1)?.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
12
lib/core/di/app_config_registration.dart
Normal file
12
lib/core/di/app_config_registration.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
class AppConfigRegistration {
|
||||
static final sl = GetIt.instance;
|
||||
|
||||
/// Регистрация AppConfig
|
||||
static void registerAppConfig({required AppBuildType buildType}) {
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: buildType));
|
||||
}
|
||||
}
|
||||
145
lib/core/di/cubit_registrations.dart
Normal file
145
lib/core/di/cubit_registrations.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
|
||||
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
/// Регистрация всех Cubit'ов в системе внедрения зависимостей
|
||||
class CubitRegistrations {
|
||||
static final sl = GetIt.instance;
|
||||
|
||||
/// Регистрация всех Cubit'ов
|
||||
static void registerAll() {
|
||||
_registerSingletonCubits();
|
||||
_registerFactoryCubits();
|
||||
}
|
||||
|
||||
/// Регистрация Cubit'ов как синглтонов
|
||||
static void _registerSingletonCubits() {
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
|
||||
sl.registerLazySingleton<InternetConnectionCubit>(() => InternetConnectionCubit());
|
||||
}
|
||||
|
||||
/// Регистрация Cubit'ов как фабрик
|
||||
static void _registerFactoryCubits() {
|
||||
// Auth related cubits
|
||||
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
|
||||
sl.registerFactory<OAuthCubit>(() => OAuthCubit(authRepository: sl()));
|
||||
// Feed related cubits
|
||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
|
||||
// Chat related cubits
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
|
||||
|
||||
// Profile related cubits
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
|
||||
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
|
||||
// Content creation cubits
|
||||
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||
|
||||
// Content viewing cubits
|
||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||
|
||||
// User content cubits
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
|
||||
|
||||
// Other feature cubits
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<DiaryCubit>(
|
||||
() => DiaryCubit(
|
||||
chatRepository: sl(),
|
||||
diaryRepository: sl(),
|
||||
glossaryRepository: sl(),
|
||||
profileRepository: sl(),
|
||||
dietRepository: sl(),
|
||||
settingsRepository: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<SettingsCubit>(
|
||||
() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()),
|
||||
);
|
||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
||||
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
|
||||
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl()));
|
||||
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
||||
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
||||
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
|
||||
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(feedRepository: sl(), repository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
|
||||
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
||||
}
|
||||
}
|
||||
33
lib/core/di/external_registrations.dart
Normal file
33
lib/core/di/external_registrations.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
|
||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
/// Регистрация внешних зависимостей в системе внедрения зависимостей
|
||||
class ExternalRegistrations {
|
||||
static final sl = GetIt.instance;
|
||||
|
||||
/// Регистрация всех внешних зависимостей
|
||||
static void registerAll() {
|
||||
_registerDio();
|
||||
}
|
||||
|
||||
/// Регистрация Dio HTTP клиента
|
||||
static void _registerDio() {
|
||||
sl.registerLazySingleton(
|
||||
() => Dio(
|
||||
BaseOptions(
|
||||
baseUrl: sl<AppConfig>().baseUrl,
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
),
|
||||
)..interceptors.addAll([
|
||||
TokenInterceptor(),
|
||||
PhoneVerifyErrorInterceptor(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
89
lib/core/di/repository_registrations.dart
Normal file
89
lib/core/di/repository_registrations.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
|
||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
|
||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
/// Регистрация всех Repository в системе внедрения зависимостей
|
||||
class RepositoryRegistrations {
|
||||
static final sl = GetIt.instance;
|
||||
|
||||
/// Регистрация всех Repository
|
||||
static void registerAll() {
|
||||
_registerSingletonRepositories();
|
||||
_registerFactoryRepositories();
|
||||
}
|
||||
|
||||
/// Регистрация Repository как синглтонов
|
||||
static void _registerSingletonRepositories() {
|
||||
// Core repositories
|
||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
|
||||
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
|
||||
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
|
||||
sl.registerLazySingleton<SettingsRepository>(() => SettingsRepository(settingsService: sl()));
|
||||
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
|
||||
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
|
||||
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
|
||||
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
|
||||
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
|
||||
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
||||
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
|
||||
sl.registerLazySingleton<RecipeRepository>(() => RecipeRepository(recipeService: sl()));
|
||||
sl.registerLazySingleton<ComplainingRepository>(() => ComplainingRepository(service: sl()));
|
||||
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(authService: sl()));
|
||||
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
|
||||
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
|
||||
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
||||
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
||||
|
||||
// Content repositories
|
||||
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => CreateDietRepository(createDietService: sl()));
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
|
||||
|
||||
// Auth repositories
|
||||
sl.registerLazySingleton<RegistrationRepository>(() => RegistrationRepository(registrationService: sl()));
|
||||
}
|
||||
|
||||
/// Регистрация Repository как фабрик
|
||||
static void _registerFactoryRepositories() {
|
||||
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
||||
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
||||
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
|
||||
}
|
||||
}
|
||||
88
lib/core/di/service_registrations.dart
Normal file
88
lib/core/di/service_registrations.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'package:eatvio_app/core/comments/services/comments_service.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
/// Регистрация всех Service в системе внедрения зависимостей
|
||||
class ServiceRegistrations {
|
||||
static final sl = GetIt.instance;
|
||||
|
||||
/// Регистрация всех Service
|
||||
static void registerAll() {
|
||||
_registerSingletonServices();
|
||||
}
|
||||
|
||||
/// Регистрация Service как синглтонов
|
||||
static void _registerSingletonServices() {
|
||||
// Core services
|
||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
|
||||
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
|
||||
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
|
||||
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
|
||||
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
|
||||
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
|
||||
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
|
||||
sl.registerLazySingleton<DietService>(() => DietService(sl()));
|
||||
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
|
||||
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
||||
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
|
||||
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
|
||||
sl.registerLazySingleton<WsService>(() => WsService(sl()));
|
||||
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
|
||||
sl.registerLazySingleton<ComplainingService>(() => ComplainingService(sl()));
|
||||
sl.registerLazySingleton<AuthService>(() => AuthService(sl()));
|
||||
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
|
||||
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
|
||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
||||
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
|
||||
|
||||
// Content services
|
||||
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
|
||||
sl.registerLazySingleton<BlogPostService>(() => BlogPostService(sl()));
|
||||
sl.registerLazySingleton<CreateDietService>(() => CreateDietService(sl()));
|
||||
|
||||
// Auth services
|
||||
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,13 @@ class EnvConfig {
|
||||
// Connection Check URLs
|
||||
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? '';
|
||||
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? '';
|
||||
|
||||
// VK ID Configuration
|
||||
static String get vkClientIdIos => dotenv.env['VK_CLIENT_ID_IOS'] ?? '';
|
||||
static String get vkClientIdAndroid => dotenv.env['VK_CLIENT_ID_ANDROID'] ?? '';
|
||||
|
||||
// Yandex ID Configuration
|
||||
static String get yandexClientId => dotenv.env['YANDEX_CLIENT_ID'] ?? '';
|
||||
static String get yandexClientSecret => dotenv.env['YANDEX_CLIENT_SECRET'] ?? '';
|
||||
static String get yandexRedirectUri => dotenv.env['YANDEX_REDIRECT_URI'] ?? '';
|
||||
}
|
||||
|
||||
@@ -1,28 +1,43 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/services.dart' show MethodChannel, PlatformException;
|
||||
|
||||
class FirebaseApi {
|
||||
final _firebaseMessaging = FirebaseMessaging.instance;
|
||||
|
||||
Future<String?> getToken() async {
|
||||
await _firebaseMessaging.requestPermission();
|
||||
return await _firebaseMessaging.getToken();
|
||||
String? token;
|
||||
|
||||
if (await _isHuaweiDevice()) {
|
||||
token = await _getHuaweiPushTokenNative();
|
||||
} else {
|
||||
if ((sl<AppConfig>().buildType == AppBuildType.prod)) {
|
||||
await _firebaseMessaging.requestPermission();
|
||||
}
|
||||
token = await _firebaseMessaging.getToken();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
Future<void> registerPushToken() async {
|
||||
if (await TokenStorage.haveTokenInStorage()) {
|
||||
try {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
final isHuawei = Platform.isAndroid ? await _isHuaweiDevice() : false;
|
||||
await sl<Dio>().post(
|
||||
'/api/profile/notification/token/register',
|
||||
data: {
|
||||
'platform': Platform.isIOS ? 'ios' : 'android',
|
||||
'platform': Platform.isIOS ? 'ios' : (isHuawei ? 'huawei' : 'android'),
|
||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||
'token': await getToken(),
|
||||
},
|
||||
@@ -32,4 +47,32 @@ class FirebaseApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _isHuaweiDevice() async {
|
||||
if (!Platform.isAndroid) return false;
|
||||
try {
|
||||
final info = await DeviceInfoPlugin().androidInfo;
|
||||
final brand = info.brand.toLowerCase();
|
||||
final manufacturer = info.manufacturer.toLowerCase();
|
||||
return brand.contains('huawei') || brand.contains('honor') || manufacturer.contains('huawei') || manufacturer.contains('honor');
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getHuaweiPushTokenNative() async {
|
||||
try {
|
||||
const channel = MethodChannel('com.eatvioapp/hms_push');
|
||||
final String? token = await channel.invokeMethod<String>('getHuaweiToken');
|
||||
return token;
|
||||
} on PlatformException catch (e, st) {
|
||||
showErrorToast('$e $st');
|
||||
log(e.toString(), stackTrace: st);
|
||||
return null;
|
||||
} catch (e, st) {
|
||||
showErrorToast('$e $st');
|
||||
log(e.toString(), stackTrace: st);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_state.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class InternetConnectionCubit extends Cubit<InternetConnectionState> {
|
||||
InternetConnectionCubit() : super(InternetConnectionState()) {
|
||||
checkConnection();
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateAccessStatus);
|
||||
_listener = AppLifecycleListener(
|
||||
onResume: () async {
|
||||
_connectivitySubscription.resume();
|
||||
await checkConnection();
|
||||
},
|
||||
onHide: _connectivitySubscription.pause,
|
||||
onPause: _connectivitySubscription.pause,
|
||||
);
|
||||
}
|
||||
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
|
||||
late final AppLifecycleListener _listener;
|
||||
|
||||
Future<void> checkConnection() async {
|
||||
late List<ConnectivityResult> result;
|
||||
|
||||
try {
|
||||
result = await _connectivity.checkConnectivity();
|
||||
} on PlatformException catch (e) {
|
||||
log('Couldn\'t check connectivity status', error: e);
|
||||
return;
|
||||
}
|
||||
|
||||
return _updateAccessStatus(result);
|
||||
}
|
||||
|
||||
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
|
||||
final hasInternet = await _checkInternetAccess(result);
|
||||
if (!hasInternet) showInternetConnectionToast();
|
||||
emit(state.copyWith(isInternetAvailable: hasInternet));
|
||||
}
|
||||
|
||||
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
|
||||
if (result.contains(ConnectivityResult.none)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final uris = [Uri.parse(EnvConfig.googleCheckUrl), Uri.parse(EnvConfig.yandexCheckUrl), Uri.parse(EnvConfig.devBaseUrl), Uri.parse(EnvConfig.baseUrl)];
|
||||
for (final uri in uris) {
|
||||
try {
|
||||
final response = await http.get(uri).timeout(const Duration(seconds: 3));
|
||||
return response.statusCode >= 100 && response.statusCode < 600;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_connectivitySubscription.cancel();
|
||||
_listener.dispose();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'internet_connection_state.freezed.dart';
|
||||
|
||||
|
||||
@freezed
|
||||
abstract class InternetConnectionState with _$InternetConnectionState {
|
||||
|
||||
factory InternetConnectionState({
|
||||
@Default(true) bool isInternetAvailable,
|
||||
}) = _InternetConnectionState;
|
||||
|
||||
}
|
||||
@@ -1,263 +1,32 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
|
||||
import 'package:eatvio_app/core/comments/services/comments_service.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/di/app_config_registration.dart';
|
||||
import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/external_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/repository_registrations.dart';
|
||||
import 'package:eatvio_app/core/di/service_registrations.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
||||
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
||||
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
|
||||
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
|
||||
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
||||
|
||||
/// Глобальный экземпляр GetIt для внедрения зависимостей
|
||||
final sl = GetIt.instance;
|
||||
|
||||
Future<void> init() async {
|
||||
// Cubit singletons
|
||||
/// Инициализация системы внедрения зависимостей
|
||||
Future<void> init({required AppBuildType buildType}) async {
|
||||
// Регистрация AppConfig
|
||||
AppConfigRegistration.registerAppConfig(buildType: buildType);
|
||||
|
||||
// Регистрация внешних зависимостей (Dio, etc.)
|
||||
ExternalRegistrations.registerAll();
|
||||
|
||||
// Регистрация сервисов
|
||||
ServiceRegistrations.registerAll();
|
||||
|
||||
// Регистрация репозиториев
|
||||
RepositoryRegistrations.registerAll();
|
||||
|
||||
// Регистрация Cubit'ов
|
||||
CubitRegistrations.registerAll();
|
||||
|
||||
// Регистрация WebSocket менеджера
|
||||
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
|
||||
|
||||
// Cubit factories
|
||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
|
||||
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
|
||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()));
|
||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||
sl.registerFactory<DiaryCubit>(
|
||||
() => DiaryCubit(
|
||||
chatRepository: sl(),
|
||||
diaryRepository: sl(),
|
||||
glossaryRepository: sl(),
|
||||
profileRepository: sl(),
|
||||
dietRepository: sl(),
|
||||
settingsRepository: sl(),
|
||||
),
|
||||
);
|
||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
||||
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
|
||||
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
|
||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
||||
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
|
||||
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl()));
|
||||
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
||||
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
||||
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
|
||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
|
||||
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
|
||||
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
|
||||
|
||||
// Repository
|
||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
|
||||
sl.registerLazySingleton<RegistrationRepository>(() => RegistrationRepository(registrationService: sl()));
|
||||
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
|
||||
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
|
||||
sl.registerLazySingleton<RecipeRepository>(() => RecipeRepository(recipeService: sl()));
|
||||
sl.registerLazySingleton<SettingsRepository>(() => SettingsRepository(settingsService: sl()));
|
||||
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
|
||||
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
|
||||
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
|
||||
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
|
||||
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => CreateDietRepository(createDietService: sl()));
|
||||
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
|
||||
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
|
||||
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
|
||||
sl.registerLazySingleton<ComplainingRepository>(() => ComplainingRepository(service: sl()));
|
||||
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(authService: sl()));
|
||||
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
|
||||
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
|
||||
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
||||
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
||||
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
||||
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
||||
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
||||
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
|
||||
|
||||
// Services
|
||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
|
||||
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
|
||||
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
|
||||
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
|
||||
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
|
||||
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
|
||||
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
|
||||
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
|
||||
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
|
||||
sl.registerLazySingleton<DietService>(() => DietService(sl()));
|
||||
sl.registerLazySingleton<BlogPostService>(() => BlogPostService(sl()));
|
||||
sl.registerLazySingleton<CreateDietService>(() => CreateDietService(sl()));
|
||||
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
|
||||
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
||||
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
|
||||
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
|
||||
sl.registerLazySingleton<WsService>(() => WsService(sl()));
|
||||
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
|
||||
sl.registerLazySingleton<ComplainingService>(() => ComplainingService(sl()));
|
||||
sl.registerLazySingleton<AuthService>(() => AuthService(sl()));
|
||||
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
|
||||
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
|
||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
||||
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
() =>
|
||||
Dio(BaseOptions(baseUrl: baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
|
||||
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
|
||||
);
|
||||
}
|
||||
|
||||
47
lib/core/routing/auth_routes.dart
Normal file
47
lib/core/routing/auth_routes.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/register_via_social_screen.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Маршруты для аутентификации
|
||||
class AuthRoutes {
|
||||
/// Создание маршрутов аутентификации
|
||||
static List<RouteBase> createRoutes() {
|
||||
return [
|
||||
GoRoute(
|
||||
path: RouteConstants.authScreen,
|
||||
name: 'authScreen',
|
||||
builder: (context, state) => const AuthScreen(),
|
||||
routes: [
|
||||
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||
GoRoute(
|
||||
path: RouteConstants.forgetPasswordScreen,
|
||||
name: 'forgetPasswordScreen',
|
||||
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
|
||||
),
|
||||
GoRoute(path: RouteConstants.scanQRCodeScreen, name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.verifyScreen,
|
||||
name: 'verifyScreen',
|
||||
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
||||
),
|
||||
GoRoute(path: RouteConstants.verifyEmailScreen, name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
|
||||
GoRoute(
|
||||
path: RouteConstants.registerViaSocialScreen,
|
||||
name: 'registerViaSocialScreen',
|
||||
builder: (context, state) {
|
||||
final extra = state.extra as Map<String, dynamic>?;
|
||||
return RegisterViaSocialScreen(authResult: extra?['authResult'] as AuthResultEntity?, provider: extra?['provider'] as String?);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
78
lib/core/routing/content_routes.dart
Normal file
78
lib/core/routing/content_routes.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Маршруты для контента
|
||||
class ContentRoutes {
|
||||
/// Создание маршрутов контента
|
||||
static List<RouteBase> createRoutes() {
|
||||
return [
|
||||
GoRoute(
|
||||
path: RouteConstants.aboutPlanScreen,
|
||||
name: 'aboutPlanScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.aboutRecipesScreen,
|
||||
name: 'aboutRecipesScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
|
||||
}
|
||||
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.aboutDietScreen,
|
||||
name: 'aboutDietScreen',
|
||||
builder: (context, state) {
|
||||
final slug = state.uri.queryParameters['slug'];
|
||||
if (slug != null) {
|
||||
return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: false);
|
||||
}
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutDietScreen(
|
||||
id: data['id'] as int?,
|
||||
slug: data['slug'] as String?,
|
||||
index: data['index'] as int?,
|
||||
navigateToComments: data['navigateToComments'] as bool,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.blogPostCreateScreen,
|
||||
name: 'blogPostCreateScreen',
|
||||
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.blogPostScreen,
|
||||
name: 'blogPostScreen',
|
||||
builder: (context, state) {
|
||||
final id = state.uri.queryParameters['id'];
|
||||
final parsedId = int.tryParse(id ?? '');
|
||||
if (parsedId != null) {
|
||||
return BlogPostScreen(id: parsedId);
|
||||
}
|
||||
|
||||
return BlogPostScreen(id: state.extra as int);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
94
lib/core/routing/main_navigation_routes.dart
Normal file
94
lib/core/routing/main_navigation_routes.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:developer';
|
||||
|
||||
/// Маршруты для основной навигации
|
||||
class MainNavigationRoutes {
|
||||
/// Создание маршрутов основной навигации
|
||||
static List<RouteBase> createRoutes() {
|
||||
return [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) {
|
||||
return BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return BottomNavigationBarWidget(profileModel: state.profile, child: child);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: RouteConstants.myProfileScreen,
|
||||
name: 'myProfileScreen',
|
||||
pageBuilder: (context, state) {
|
||||
return const NoTransitionPage(child: ProfileScreen());
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.feedScreen,
|
||||
name: 'feedScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.diaryScreen,
|
||||
name: 'diaryScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultationsScreen.path,
|
||||
name: ConsultationsScreen.name,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.chatSelectScreen,
|
||||
name: 'chatSelectScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.notificationsScreen,
|
||||
name: 'notificationsScreen',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.menuScreen,
|
||||
name: 'menuScreen',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return MenuScreen(profileModel: state.profile);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
229
lib/core/routing/other_routes.dart
Normal file
229
lib/core/routing/other_routes.dart
Normal file
@@ -0,0 +1,229 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
|
||||
import 'package:eatvio_app/features/support/presentation/pages/support_screen.dart';
|
||||
import 'package:eatvio_app/features/update_screen/presentation/update_screen.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
/// Остальные маршруты приложения
|
||||
class OtherRoutes {
|
||||
/// Создание остальных маршрутов
|
||||
static List<RouteBase> createRoutes() {
|
||||
return [
|
||||
// Welcome and update screens
|
||||
GoRoute(
|
||||
path: RouteConstants.welcomeScreen,
|
||||
name: 'welcomeScreen',
|
||||
builder: (context, state) => const WelcomeScreen(),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
if (welcomeScreenWasShowed == false) {
|
||||
return null;
|
||||
} else if (await TokenStorage.haveTokenInStorage()) {
|
||||
return RouteConstants.feedScreen;
|
||||
} else {
|
||||
return RouteConstants.authScreen;
|
||||
}
|
||||
} catch (e) {
|
||||
return RouteConstants.feedScreen;
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.updateScreen,
|
||||
name: 'updateScreen',
|
||||
builder: (context, state) => const UpdateScreen(),
|
||||
redirect: (context, state) async {
|
||||
try {
|
||||
final versions = (await sl<Dio>().get('${sl<AppConfig>().baseUrl}/api/app_versions')).data;
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
String version = packageInfo.version;
|
||||
Version currentVersion = Version.parse(version);
|
||||
Version latestVersion = Version.parse(versions['min_version']);
|
||||
if (latestVersion > currentVersion) {
|
||||
return null;
|
||||
} else {
|
||||
return RouteConstants.welcomeScreen;
|
||||
}
|
||||
} catch (e) {
|
||||
return RouteConstants.welcomeScreen;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// Archive routes
|
||||
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
|
||||
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
|
||||
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
|
||||
|
||||
// Finance routes
|
||||
GoRoute(path: RouteConstants.userFinanceScreen, name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
|
||||
GoRoute(
|
||||
path: CardSettingsScreen.path,
|
||||
name: CardSettingsScreen.name,
|
||||
builder: (context, state) {
|
||||
final chosenPlanId = state.extra as int?;
|
||||
return CardSettingsScreen(chosenPlanId: chosenPlanId);
|
||||
},
|
||||
),
|
||||
|
||||
// Content creation routes
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
name: RecipeCreateScreen.name,
|
||||
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
||||
),
|
||||
GoRoute(
|
||||
path: CreateDietScreen.path,
|
||||
name: CreateDietScreen.name,
|
||||
builder: (context, state) {
|
||||
final diet = state.extra as Diet?;
|
||||
return CreateDietScreen(diet: diet);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: DietPreviewScreen.path,
|
||||
name: DietPreviewScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Diet;
|
||||
return DietPreviewScreen(diet: data);
|
||||
},
|
||||
),
|
||||
GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()),
|
||||
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
|
||||
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
|
||||
GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
|
||||
|
||||
// Profile routes
|
||||
GoRoute(
|
||||
path: RouteConstants.profileScreen,
|
||||
name: 'profileScreen',
|
||||
builder: (context, state) => ProfileScreen(author: state.extra as Author),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.subsScreen,
|
||||
name: 'subsScreen',
|
||||
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
|
||||
),
|
||||
|
||||
// Chat routes
|
||||
GoRoute(
|
||||
path: RouteConstants.chatScreen,
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final uuid = data['uuid'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.emptyChatScreen,
|
||||
name: 'emptyChatScreen',
|
||||
builder: (context, state) => EmptyChatScreen(author: state.extra as Author),
|
||||
),
|
||||
|
||||
// Consultant routes
|
||||
GoRoute(path: RouteConstants.becomeConsultantScreen, name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
|
||||
GoRoute(
|
||||
path: RouteConstants.stepperBecomeConsultantScreen,
|
||||
name: 'stepperBecomeConsultantScreen',
|
||||
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
|
||||
),
|
||||
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||
GoRoute(
|
||||
path: RouteConstants.photoViewScreen,
|
||||
name: 'photoViewScreen',
|
||||
builder: (context, state) => PhotoViewScreen(imageUrls: state.extra as List<String>, index: int.parse(state.pathParameters['index']!)),
|
||||
),
|
||||
|
||||
// Other routes
|
||||
GoRoute(path: RouteConstants.glossaryScreen, name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
|
||||
GoRoute(path: RouteConstants.blockedUsersScreen, name: 'blockedUsersScreen', builder: (context, state) => const BlockedUsersScreen()),
|
||||
GoRoute(
|
||||
path: RouteConstants.notificationsSettingsScreen,
|
||||
name: 'notificationsSettingsScreen',
|
||||
builder: (context, state) => const NotificationsSettingsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: UserDetailsScreen.path,
|
||||
name: UserDetailsScreen.name,
|
||||
builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
|
||||
),
|
||||
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
|
||||
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
|
||||
GoRoute(
|
||||
path: InfoByOrderScreen.path,
|
||||
name: InfoByOrderScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final successfulPayment = data['successfulPayment'] as bool?;
|
||||
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: PaymentScreen.path,
|
||||
name: PaymentScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final planId = data['planId'] as int;
|
||||
final url = data['url'] as String;
|
||||
return PaymentScreen(url: url, planId: planId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: UserDiaryScreen.path,
|
||||
name: UserDiaryScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, int>;
|
||||
final diaryId = data['diaryId'] as int;
|
||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
||||
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
85
lib/core/routing/route_constants.dart
Normal file
85
lib/core/routing/route_constants.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
/// Константы маршрутов приложения
|
||||
class RouteConstants {
|
||||
// Auth routes
|
||||
static const String authScreen = '/auth-screen';
|
||||
static const String registrationScreen = 'registration-screen';
|
||||
static const String forgetPasswordScreen = 'forget-password-screen';
|
||||
static const String scanQRCodeScreen = 'scan-qr-code-screen';
|
||||
static const String verifyScreen = '/verify-screen';
|
||||
static const String verifyEmailScreen = '/verify-email-screen';
|
||||
static const String registerViaSocialScreen = '/register-via-social-screen';
|
||||
|
||||
// Main navigation routes
|
||||
static const String myProfileScreen = '/my-profile-screen';
|
||||
static const String feedScreen = '/feed-screen';
|
||||
static const String diaryScreen = '/diary-screen';
|
||||
static const String consultationsScreen = '/consultations-screen';
|
||||
static const String chatSelectScreen = '/chat-select-screen';
|
||||
static const String notificationsScreen = '/notifications-screen';
|
||||
static const String menuScreen = '/menu-screen';
|
||||
|
||||
// Content routes
|
||||
static const String aboutPlanScreen = '/about-plan-screen';
|
||||
static const String aboutRecipesScreen = '/about-recipes-screen';
|
||||
static const String aboutDietScreen = '/about-diet-screen';
|
||||
static const String blogPostScreen = '/blog-post-screen';
|
||||
static const String blogPostCreateScreen = '/blog-post-create-screen';
|
||||
|
||||
// Profile routes
|
||||
static const String profileScreen = '/profile-screen';
|
||||
static const String subsScreen = '/subs-screen';
|
||||
|
||||
// Settings routes
|
||||
static const String settingsScreen = '/settings-screen';
|
||||
static const String generalInfoScreen = 'general-info-screen';
|
||||
static const String otherInfoScreen = 'other-info-screen';
|
||||
static const String physicalActivityScreen = 'physical-activity-screen';
|
||||
static const String physicalFormScreen = 'physical-form-screen';
|
||||
static const String pressureScreen = 'pressure-screen';
|
||||
static const String changePasswordScreen = 'change-password-screen';
|
||||
static const String changeEmailScreen = 'change-email-screen';
|
||||
|
||||
// Consultant routes
|
||||
static const String becomeConsultantScreen = '/become-consultant-screen';
|
||||
static const String stepperBecomeConsultantScreen = '/stepper-become-consultant-screen';
|
||||
static const String consultantRatingScreen = '/consultant-rating-screen';
|
||||
|
||||
// Other routes
|
||||
static const String welcomeScreen = '/welcome-screen';
|
||||
static const String updateScreen = '/update-screen';
|
||||
static const String calendarScreen = '/calendar-screen';
|
||||
static const String supportScreen = '/support-screen';
|
||||
static const String photoViewScreen = '/photo-view-screen';
|
||||
static const String chatScreen = '/chat-screen';
|
||||
static const String emptyChatScreen = '/empty-chat-screen';
|
||||
static const String glossaryScreen = '/glossary-screen';
|
||||
static const String blockedUsersScreen = '/blocked-users-screen';
|
||||
static const String notificationsSettingsScreen = '/notifications-settings-screen';
|
||||
|
||||
// Finance routes
|
||||
static const String userFinanceScreen = '/user-finance-screen';
|
||||
static const String cardSettingsScreen = '/card-settings-screen';
|
||||
static const String orderInfoScreen = '/order-info-screen';
|
||||
static const String paymentScreen = '/payment-screen';
|
||||
|
||||
// Content creation routes
|
||||
static const String recipeCreateScreen = '/recipe-create-screen';
|
||||
static const String createDietScreen = '/create-diet-screen';
|
||||
static const String dietPreviewScreen = '/diet-preview-screen';
|
||||
static const String createPlanScreen = '/create-plan-screen';
|
||||
static const String myDietsScreen = '/my-diets-screen';
|
||||
static const String myPlansScreen = '/my-plans-screen';
|
||||
static const String myRecipesScreen = '/my-recipes-screen';
|
||||
|
||||
// User details
|
||||
static const String userDetailsScreen = '/user-details-screen';
|
||||
static const String userDiaryScreen = '/user-diary-screen';
|
||||
|
||||
// Dev tools
|
||||
static const String devToolsScreen = '/dev-tools-screen';
|
||||
|
||||
// Archive routes
|
||||
static const String consultationsArchiveScreen = '/consultations-archive-screen';
|
||||
static const String consultantFinancesScreen = '/consultant-finances-screen';
|
||||
static const String userPurchasesScreen = '/user-purchases-screen';
|
||||
}
|
||||
62
lib/core/routing/settings_routes.dart
Normal file
62
lib/core/routing/settings_routes.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/change_password_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/general_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/other_info_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_activity_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Маршруты для настроек
|
||||
class SettingsRoutes {
|
||||
/// Создание маршрутов настроек
|
||||
static List<RouteBase> createRoutes() {
|
||||
return [
|
||||
GoRoute(
|
||||
path: RouteConstants.settingsScreen,
|
||||
name: 'settingsScreen',
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: RouteConstants.generalInfoScreen,
|
||||
name: 'generalInfoScreen',
|
||||
builder: (context, state) => GeneralInfoScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.otherInfoScreen,
|
||||
name: 'otherInfoScreen',
|
||||
builder: (context, state) => OtherInfoScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.physicalActivityScreen,
|
||||
name: 'physicalActivityScreen',
|
||||
builder: (context, state) => PhysicalActivityScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.physicalFormScreen,
|
||||
name: 'physicalFormScreen',
|
||||
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.pressureScreen,
|
||||
name: 'pressureScreen',
|
||||
builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.changePasswordScreen,
|
||||
name: 'changePasswordScreen',
|
||||
builder: (context, state) => ChangePasswordScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteConstants.changeEmailScreen,
|
||||
name: 'changeEmailScreen',
|
||||
builder: (context, state) => ChangeEmailScreen(settingsEntity: state.extra as SettingsEntity),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -204,6 +204,8 @@ class PrimaryColors {
|
||||
Color get commentBg => const Color(0XFFFFFDD8);
|
||||
|
||||
Color get cardNotificationsBG => const Color(0xFFA4A4A4);
|
||||
|
||||
Color get grey7B => const Color(0xFF7B7B7B);
|
||||
}
|
||||
|
||||
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
||||
|
||||
@@ -38,4 +38,16 @@ void showSuccessToast(String message) {
|
||||
textColor: appTheme.whiteA700,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showInternetConnectionToast() {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Проверьте подключение к интернету',
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.TOP,
|
||||
timeInSecForIosWeb: 3,
|
||||
backgroundColor: appTheme.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
|
||||
87
lib/core/utils/validation_utils.dart
Normal file
87
lib/core/utils/validation_utils.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:eatvio_app/core/constants/app_constants.dart';
|
||||
|
||||
/// Утилиты для валидации
|
||||
class ValidationUtils {
|
||||
/// Валидация email
|
||||
static bool isValidEmail(String email) {
|
||||
if (email.isEmpty) return false;
|
||||
final regex = RegExp(AppConstants.emailRegex);
|
||||
return regex.hasMatch(email);
|
||||
}
|
||||
|
||||
/// Валидация телефона
|
||||
static bool isValidPhone(String phone) {
|
||||
if (phone.isEmpty) return false;
|
||||
final regex = RegExp(AppConstants.phoneRegex);
|
||||
return regex.hasMatch(phone);
|
||||
}
|
||||
|
||||
/// Валидация URL
|
||||
static bool isValidUrl(String url) {
|
||||
if (url.isEmpty) return false;
|
||||
final regex = RegExp(AppConstants.urlRegex);
|
||||
return regex.hasMatch(url);
|
||||
}
|
||||
|
||||
/// Валидация длины текста
|
||||
static bool isValidTextLength(String text, {int? maxLength}) {
|
||||
final length = maxLength ?? AppConstants.maxTextLength;
|
||||
return text.length <= length;
|
||||
}
|
||||
|
||||
/// Валидация длины заголовка
|
||||
static bool isValidTitleLength(String title) {
|
||||
return title.isNotEmpty && title.length <= AppConstants.maxTitleLength;
|
||||
}
|
||||
|
||||
/// Валидация длины описания
|
||||
static bool isValidDescriptionLength(String description) {
|
||||
return description.length <= AppConstants.maxDescriptionLength;
|
||||
}
|
||||
|
||||
/// Валидация пароля
|
||||
static bool isValidPassword(String password) {
|
||||
// Минимум 8 символов, хотя бы одна буква и одна цифра
|
||||
if (password.length < 8) return false;
|
||||
final hasLetter = RegExp(r'[a-zA-Z]').hasMatch(password);
|
||||
final hasDigit = RegExp(r'[0-9]').hasMatch(password);
|
||||
return hasLetter && hasDigit;
|
||||
}
|
||||
|
||||
/// Валидация имени пользователя
|
||||
static bool isValidUsername(String username) {
|
||||
// От 3 до 20 символов, только буквы, цифры и подчеркивания
|
||||
if (username.length < 3 || username.length > 20) return false;
|
||||
final regex = RegExp(r'^[a-zA-Z0-9_]+$');
|
||||
return regex.hasMatch(username);
|
||||
}
|
||||
|
||||
/// Получение сообщения об ошибке валидации
|
||||
static String? getValidationErrorMessage(String? value, ValidationType type) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Поле не может быть пустым';
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ValidationType.email:
|
||||
return isValidEmail(value) ? null : 'Введите корректный email';
|
||||
case ValidationType.phone:
|
||||
return isValidPhone(value) ? null : 'Введите корректный номер телефона';
|
||||
case ValidationType.url:
|
||||
return isValidUrl(value) ? null : 'Введите корректный URL';
|
||||
case ValidationType.password:
|
||||
return isValidPassword(value) ? null : 'Пароль должен содержать минимум 8 символов, включая буквы и цифры';
|
||||
case ValidationType.username:
|
||||
return isValidUsername(value) ? null : 'Имя пользователя должно содержать от 3 до 20 символов (буквы, цифры, подчеркивания)';
|
||||
case ValidationType.title:
|
||||
return isValidTitleLength(value) ? null : 'Заголовок не может быть пустым и должен содержать не более ${AppConstants.maxTitleLength} символов';
|
||||
case ValidationType.description:
|
||||
return isValidDescriptionLength(value) ? null : 'Описание не может содержать более ${AppConstants.maxDescriptionLength} символов';
|
||||
case ValidationType.text:
|
||||
return isValidTextLength(value) ? null : 'Текст не может содержать более ${AppConstants.maxTextLength} символов';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Типы валидации
|
||||
enum ValidationType { email, phone, url, password, username, title, description, text }
|
||||
@@ -3,6 +3,7 @@ import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||
@@ -111,7 +112,7 @@ class WsManager {
|
||||
_privateNotificationChannel = client?.privateChannel(
|
||||
'$newNotificationsChannelName.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
|
||||
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
@@ -124,7 +125,7 @@ class WsManager {
|
||||
_privateMessagesChannel = client?.privateChannel(
|
||||
'$newMessagesChannelName.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
|
||||
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
@@ -137,7 +138,7 @@ class WsManager {
|
||||
_privateDirectChatChannel = client?.privateChannel(
|
||||
'$directChatChannelName.$chatId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
|
||||
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
@@ -86,7 +86,7 @@ class CustomPopup extends StatelessWidget {
|
||||
|
||||
if (isAuthor) {
|
||||
addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () {
|
||||
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
|
||||
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
|
||||
showInfoToast(s.link_was_copied);
|
||||
});
|
||||
addItem('assets/icons/share2.svg', 'Поделиться', () {
|
||||
@@ -94,7 +94,7 @@ class CustomPopup extends StatelessWidget {
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse(
|
||||
'$baseUrl/ru/$path',
|
||||
'${sl<AppConfig>().baseUrl}/ru/$path',
|
||||
),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
@@ -140,7 +140,7 @@ class CustomPopup extends StatelessWidget {
|
||||
});
|
||||
} else {
|
||||
addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () {
|
||||
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
|
||||
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
|
||||
showInfoToast(s.link_was_copied);
|
||||
});
|
||||
addItem('assets/icons/share2.svg', 'Поделиться', () {
|
||||
@@ -148,7 +148,7 @@ class CustomPopup extends StatelessWidget {
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse(
|
||||
'$baseUrl/ru/$path',
|
||||
'${sl<AppConfig>().baseUrl}/ru/$path',
|
||||
),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
|
||||
51
lib/features/auth/entities/auth_result_entity.dart
Normal file
51
lib/features/auth/entities/auth_result_entity.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:vk_id/vk_id.dart';
|
||||
|
||||
part 'auth_result_entity.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class AuthResultEntity with _$AuthResultEntity {
|
||||
const factory AuthResultEntity({
|
||||
required String accessToken,
|
||||
required String userId,
|
||||
String? refreshToken,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? email,
|
||||
String? phone,
|
||||
GenderEnum? gender,
|
||||
String? birthDate,
|
||||
}) = _AuthResultEntity;
|
||||
}
|
||||
|
||||
enum GenderEnum {
|
||||
male,
|
||||
female,
|
||||
notSet;
|
||||
|
||||
static GenderEnum? fromVkGender(VkSex? vkGender) {
|
||||
switch (vkGender) {
|
||||
case VkSex.male:
|
||||
return GenderEnum.male;
|
||||
case VkSex.female:
|
||||
return GenderEnum.female;
|
||||
case VkSex.notStated:
|
||||
return GenderEnum.notSet;
|
||||
case null:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static GenderEnum? fromString(String? gender) {
|
||||
switch (gender) {
|
||||
case 'male':
|
||||
return GenderEnum.male;
|
||||
case 'female':
|
||||
return GenderEnum.female;
|
||||
case 'notSet':
|
||||
return GenderEnum.notSet;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/features/auth/models/oauth_login_request.dart
Normal file
15
lib/features/auth/models/oauth_login_request.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'oauth_login_request.freezed.dart';
|
||||
part 'oauth_login_request.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class OAuthLoginRequest with _$OAuthLoginRequest {
|
||||
const factory OAuthLoginRequest({
|
||||
required String provider,
|
||||
@JsonKey(name: 'access_token') required String accessToken,
|
||||
@JsonKey(name: 'refresh_token') required String refreshToken,
|
||||
}) = _OAuthLoginRequest;
|
||||
|
||||
factory OAuthLoginRequest.fromJson(Map<String, dynamic> json) => _$OAuthLoginRequestFromJson(json);
|
||||
}
|
||||
24
lib/features/auth/models/oauth_model.dart
Normal file
24
lib/features/auth/models/oauth_model.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'oauth_model.freezed.dart';
|
||||
part 'oauth_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class OAuthModel with _$OAuthModel {
|
||||
const factory OAuthModel({
|
||||
required String provider,
|
||||
required String id,
|
||||
@JsonKey(name: 'access_token') required String accessToken,
|
||||
@JsonKey(name: 'refresh_token') required String refreshToken,
|
||||
@JsonKey(name: 'first_name') required String firstName,
|
||||
@JsonKey(name: 'last_name') required String lastName,
|
||||
required String email,
|
||||
required String phone,
|
||||
@JsonKey(name: 'date_of_birth') required String dateOfBirth,
|
||||
required String gender,
|
||||
required int weight,
|
||||
required int height,
|
||||
}) = _OAuthModel;
|
||||
|
||||
factory OAuthModel.fromJson(Map<String, dynamic> json) => _$OAuthModelFromJson(json);
|
||||
}
|
||||
12
lib/features/auth/models/social_exchange_request.dart
Normal file
12
lib/features/auth/models/social_exchange_request.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
class SocialExchangeRequest {
|
||||
final String provider;
|
||||
final String accessToken;
|
||||
|
||||
SocialExchangeRequest({required this.provider, required this.accessToken});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'provider': provider, 'access_token': accessToken};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
lib/features/auth/models/yandex_profile_model.dart
Normal file
30
lib/features/auth/models/yandex_profile_model.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'yandex_profile_model.freezed.dart';
|
||||
part 'yandex_profile_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class YandexProfileModel with _$YandexProfileModel {
|
||||
factory YandexProfileModel({
|
||||
required String id,
|
||||
required String login,
|
||||
required String birthday,
|
||||
@JsonKey(name: 'client_id') required String clientId,
|
||||
@JsonKey(name: 'display_name') required String displayName,
|
||||
@JsonKey(name: 'real_name') required String realName,
|
||||
@JsonKey(name: 'first_name') required String firstName,
|
||||
@JsonKey(name: 'last_name') required String lastName,
|
||||
@JsonKey(name: 'sex') required String sex,
|
||||
@JsonKey(name: 'default_email') required String defaultEmail,
|
||||
@JsonKey(name: 'default_phone') required YandexPhoneData defaultPhone,
|
||||
}) = _YandexProfileModel;
|
||||
|
||||
factory YandexProfileModel.fromJson(Map<String, dynamic> json) => _$YandexProfileModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class YandexPhoneData with _$YandexPhoneData {
|
||||
factory YandexPhoneData({required int id, required String number}) = _YandexPhoneData;
|
||||
|
||||
factory YandexPhoneData.fromJson(Map<String, dynamic> json) => _$YandexPhoneDataFromJson(json);
|
||||
}
|
||||
@@ -6,28 +6,17 @@ import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state
|
||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
|
||||
class AuthCubit extends Cubit<AuthState> {
|
||||
final AuthRepository authRepository;
|
||||
|
||||
AuthCubit({
|
||||
required this.authRepository,
|
||||
}) : super(const AuthState());
|
||||
AuthCubit({required this.authRepository}) : super(const AuthState());
|
||||
|
||||
Future<bool> getTokenByCredentials(String email, String password) async {
|
||||
emit(const AuthState.loading());
|
||||
try {
|
||||
final token = (await authRepository.getTokenByCredentials(
|
||||
email,
|
||||
password,
|
||||
))
|
||||
.token;
|
||||
emit(
|
||||
AuthState.loaded(
|
||||
token: token,
|
||||
),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
final token = (await authRepository.getTokenByCredentials(email, password)).token;
|
||||
emit(AuthState.loaded(token: token));
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
@@ -36,4 +25,19 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> getTokenBySocial({required String provider, required String accessToken}) async {
|
||||
emit(const AuthState.loading());
|
||||
try {
|
||||
final token = (await authRepository.getTokenBySocial(provider: provider, accessToken: accessToken)).token;
|
||||
emit(AuthState.loaded(token: token));
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Не удалось войти через $provider');
|
||||
emit(AuthState.error(errorMessage: '$e'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'dart:developer';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class OAuthCubit extends Cubit<OAuthState> {
|
||||
final AuthRepository authRepository;
|
||||
|
||||
OAuthCubit({required this.authRepository}) : super(const OAuthState());
|
||||
|
||||
Future<void> getOAuthLogin({required String provider, required String accessToken, required String refreshToken}) async {
|
||||
try {
|
||||
log('OAuthCubit: Starting OAuth login for provider: $provider');
|
||||
emit(const OAuthState.loading());
|
||||
log('OAuthCubit: Emitted loading state');
|
||||
|
||||
final tokenModel = await authRepository.getOAuthLogin(provider: provider, accessToken: accessToken, refreshToken: refreshToken);
|
||||
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
||||
emit(OAuthState.loaded(token: tokenModel.token));
|
||||
log('OAuthCubit: Emitted loaded state');
|
||||
} catch (e) {
|
||||
log('OAuthCubit: Error occurred: $e');
|
||||
emit(OAuthState.error(errorMessage: e.toString()));
|
||||
log('OAuthCubit: Emitted error state');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getOAuthToken({
|
||||
required String provider,
|
||||
required String accessToken,
|
||||
required String id,
|
||||
required String refreshToken,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required String email,
|
||||
required String phone,
|
||||
required String dateOfBirth,
|
||||
required String gender,
|
||||
required int weight,
|
||||
required int height,
|
||||
}) async {
|
||||
try {
|
||||
emit(const OAuthState.loading());
|
||||
final token = await authRepository.getOAuthToken(
|
||||
provider: provider,
|
||||
accessToken: accessToken,
|
||||
id: id,
|
||||
refreshToken: refreshToken,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
dateOfBirth: dateOfBirth,
|
||||
gender: gender,
|
||||
weight: weight,
|
||||
height: height,
|
||||
);
|
||||
emit(OAuthState.loaded(token: token));
|
||||
} catch (e) {
|
||||
emit(OAuthState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'oauth_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class OAuthState with _$OAuthState {
|
||||
const factory OAuthState() = _OAuthState;
|
||||
|
||||
const factory OAuthState.loading() = OAuthStateLoading;
|
||||
|
||||
const factory OAuthState.loaded({required String token}) = OAuthStateLoaded;
|
||||
|
||||
const factory OAuthState.error({required String errorMessage}) = OAuthStateError;
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
// ignore_for_file: unused_element, duplicate_ignore
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
@@ -7,9 +12,14 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
@@ -24,6 +34,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:eatvio_app/features/auth/sdk/social_auth_facade.dart';
|
||||
|
||||
class AuthScreen extends StatefulWidget {
|
||||
const AuthScreen({super.key});
|
||||
@@ -41,7 +52,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
late StreamSubscription<bool> keyboardSubscription;
|
||||
bool isFocused = false;
|
||||
int tapsCount = 0;
|
||||
|
||||
bool _isLoading = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -87,6 +98,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var shortestSide = MediaQuery.sizeOf(context).shortestSide;
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final bool useMobileLayout = shortestSide < 600;
|
||||
if (!useMobileLayout) isFocused = true;
|
||||
final s = S.of(context);
|
||||
@@ -97,9 +109,10 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
builder: (BuildContext context, AuthState state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: screenSize.height * 0.15,
|
||||
centerTitle: true,
|
||||
toolbarHeight: isFocused ? 64 : MediaQuery.of(context).size.width - 64,
|
||||
title: GestureDetector(
|
||||
key: AppKeys.hiddenLogoButtonAuthScreen,
|
||||
onTap: () {
|
||||
tapsCount++;
|
||||
if (tapsCount == 7) {
|
||||
@@ -107,7 +120,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: isFocused ? 64 : MediaQuery.of(context).size.width - 64, fit: BoxFit.contain),
|
||||
child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: screenSize.height * 0.20),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
@@ -116,6 +129,42 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Social auth buttons at the top (outlined)
|
||||
const Center(child: Text('Авторизация через')),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_SocialButton(
|
||||
onTap: () => _onVkPressed(),
|
||||
backgroundColor: const Color(0xFF2787F5),
|
||||
tooltip: 'VK ID',
|
||||
child: SvgPicture.asset('assets/icons/vk_logo.svg', height: 24, width: 24),
|
||||
),
|
||||
_SocialButton(
|
||||
onTap: _onOkPressed,
|
||||
backgroundColor: const Color(0xFFF58220),
|
||||
tooltip: 'Одноклассники',
|
||||
child: SvgPicture.asset('assets/icons/ok_logo.svg', height: 24, width: 24),
|
||||
),
|
||||
_SocialButton(
|
||||
onTap: _onMailRuPressed,
|
||||
backgroundColor: const Color(0xFF005FF9),
|
||||
tooltip: 'Mail.ru',
|
||||
child: SvgPicture.asset('assets/icons/mail_logo.svg', height: 24, width: 24),
|
||||
),
|
||||
_SocialButton(
|
||||
onTap: _onYandexPressed,
|
||||
backgroundColor: const Color(0xFFFFCC00),
|
||||
tooltip: 'Яндекс ID',
|
||||
child: SvgPicture.asset('assets/icons/yandex_logo.svg', height: 24, width: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 16),
|
||||
Text(s.email),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
@@ -123,6 +172,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
textInputType: TextInputType.emailAddress,
|
||||
key: AppKeys.emailTextFieldAuthScreen,
|
||||
hintText: 'Введите email',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -131,6 +181,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
CustomTextFormField(
|
||||
obscureText: hidePassword,
|
||||
controller: passwordController,
|
||||
key: AppKeys.passwordTextFieldAuthScreen,
|
||||
hintText: 'Введите пароль',
|
||||
suffix: IconButton(
|
||||
onPressed: () {
|
||||
@@ -183,7 +234,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/oferta_bs.pdf')),
|
||||
),
|
||||
const TextSpan(text: ' и '),
|
||||
],
|
||||
@@ -195,7 +246,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/docs/privacy.pdf')),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -208,21 +259,28 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width - 88,
|
||||
child: FloatingActionButton.extended(
|
||||
key: AppKeys.signInButtonAuthScreen,
|
||||
heroTag: 'authButton',
|
||||
highlightElevation: 0,
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||
onPressed: state is AuthStateLoading
|
||||
backgroundColor: (state is AuthStateLoading || _isLoading) ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||
onPressed: (state is AuthStateLoading || _isLoading)
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text);
|
||||
if (isAuthenticated) {
|
||||
if (context.mounted) {
|
||||
_reconnectWebSocket(context);
|
||||
await _reconnectWebSocket(context);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
},
|
||||
label: state is AuthStateLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
@@ -255,7 +313,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
children: [
|
||||
const Text('Где взять QR-код?\n', style: TextStyle(fontSize: 18)),
|
||||
Text(
|
||||
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
|
||||
'1. Войдите на сайте ${sl<AppConfig>().baseUrl} в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
@@ -317,3 +375,173 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SocialButton extends StatelessWidget {
|
||||
const _SocialButton({required this.onTap, required this.child, required this.backgroundColor, required this.tooltip});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Widget child;
|
||||
final Color backgroundColor;
|
||||
final String tooltip;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: tooltip,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: onTap,
|
||||
child: Ink(
|
||||
height: 48,
|
||||
width: (MediaQuery.of(context).size.width - 16 * 2 - 12 * 3) / 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: const Color(0xFFDBDBDB), width: 1),
|
||||
),
|
||||
child: Center(child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onVkPressed() async {
|
||||
try {
|
||||
log('VK button pressed');
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) {
|
||||
log('Context is null');
|
||||
return;
|
||||
}
|
||||
|
||||
log('Getting VK auth result');
|
||||
final authResult = await SocialAuthFacade.signInWithVkAndFetchProfile(context: context);
|
||||
if (authResult == null) {
|
||||
log('VK auth result is null');
|
||||
return;
|
||||
}
|
||||
|
||||
log('VK auth result received, trying OAuth login');
|
||||
// Пробуем сначала авторизоваться через новый API
|
||||
if (context.mounted) {
|
||||
await _tryOAuthLogin(context, authResult, 'vk');
|
||||
} else {
|
||||
log('Context is not mounted');
|
||||
}
|
||||
} catch (e) {
|
||||
log('VK button error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _onOkPressed() async {
|
||||
try {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
final authResult = await SocialAuthFacade.signInWithOkAndFetchProfile();
|
||||
if (authResult == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Пробуем сначала авторизоваться через новый API
|
||||
if (context.mounted) {
|
||||
await _tryOAuthLogin(context, authResult, 'ok');
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _onMailRuPressed() async {
|
||||
try {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
final authResult = await SocialAuthFacade.signInWithMailRuAndFetchProfile();
|
||||
if (authResult == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Пробуем сначала авторизоваться через новый API
|
||||
if (context.mounted) {
|
||||
await _tryOAuthLogin(context, authResult, 'mailru');
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _onYandexPressed() async {
|
||||
try {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
|
||||
final authResult = await SocialAuthFacade.signInWithYandex();
|
||||
if (authResult == null) return;
|
||||
|
||||
// Пробуем сначала авторизоваться через новый API
|
||||
if (context.mounted) {
|
||||
await _tryOAuthLogin(context, authResult, 'yandex');
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Пробует авторизоваться через новый OAuth login API
|
||||
/// Если получает ошибку (включая 422 - пользователь не найден), переходит к регистрации
|
||||
Future<void> _tryOAuthLogin(BuildContext context, AuthResultEntity authResult, String provider) async {
|
||||
log('Starting OAuth login for provider: $provider');
|
||||
|
||||
try {
|
||||
// Создаем OAuth cubit
|
||||
final oauthCubit = CubitRegistrations.sl<OAuthCubit>();
|
||||
StreamSubscription? subscription;
|
||||
|
||||
// Слушаем изменения состояния
|
||||
subscription = oauthCubit.stream.listen((state) {
|
||||
log('OAuth state changed: ${state.runtimeType}');
|
||||
|
||||
if (state is OAuthStateLoaded) {
|
||||
// Успешная авторизация - переходим на главный экран
|
||||
log('OAuth login successful, navigating to main screen');
|
||||
subscription?.cancel();
|
||||
_reconnectAfterAuth();
|
||||
} else if (state is OAuthStateError) {
|
||||
// Ошибка авторизации (включая 422 - пользователь не найден) - переходим к регистрации
|
||||
log('OAuth login failed: ${state.errorMessage}');
|
||||
subscription?.cancel();
|
||||
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: provider);
|
||||
}
|
||||
});
|
||||
|
||||
// Пробуем авторизоваться
|
||||
log('Calling getOAuthLogin with provider: $provider');
|
||||
await oauthCubit.getOAuthLogin(provider: provider, accessToken: authResult.accessToken, refreshToken: authResult.refreshToken ?? '');
|
||||
|
||||
// Ждем немного, чтобы состояние успело обновиться
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} catch (e) {
|
||||
// В случае любой ошибки (включая 422 - пользователь не найден) переходим к регистрации
|
||||
log('OAuth login error in catch block: $e');
|
||||
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: provider);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reconnectAfterAuth() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<ChatsCubit>().connectToWebSocket();
|
||||
await sl<NotificationsCubit>().getNotifications();
|
||||
await sl<NotificationsCubit>().connectToWebSocket();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешный вход');
|
||||
} catch (_) {}
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/di/cubit_registrations.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class RegisterViaSocialScreen extends StatefulWidget {
|
||||
final AuthResultEntity? authResult;
|
||||
final String? provider;
|
||||
|
||||
const RegisterViaSocialScreen({super.key, this.authResult, this.provider});
|
||||
|
||||
@override
|
||||
State<RegisterViaSocialScreen> createState() => _RegisterViaSocialScreenState();
|
||||
}
|
||||
|
||||
class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
|
||||
final maskFormatter = MaskTextInputFormatter(mask: '+7 (###) ### ## ##', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
|
||||
final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
|
||||
|
||||
// Контроллеры для полей ввода
|
||||
final firstNameController = TextEditingController();
|
||||
final lastNameController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
final phoneController = TextEditingController();
|
||||
final dateOfBirthController = TextEditingController();
|
||||
final weightController = TextEditingController();
|
||||
final heightController = TextEditingController();
|
||||
|
||||
// Состояние для выбора пола
|
||||
bool isMaleSelected = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeFieldsFromAuthResult();
|
||||
}
|
||||
|
||||
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
await sl<WsManager>().initialize();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
if (context.mounted) {
|
||||
try {
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||
await sl<ChatsCubit>().connectToWebSocket();
|
||||
await sl<NotificationsCubit>().getNotifications();
|
||||
await sl<NotificationsCubit>().connectToWebSocket();
|
||||
await sl<FirebaseApi>().registerPushToken();
|
||||
showSuccessToast('Успешная регистрация');
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
AppRouter.appRouter.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
}
|
||||
|
||||
void _initializeFieldsFromAuthResult() {
|
||||
if (widget.authResult != null) {
|
||||
final authResult = widget.authResult!;
|
||||
|
||||
if (authResult.firstName != null) {
|
||||
firstNameController.text = authResult.firstName!;
|
||||
}
|
||||
if (authResult.lastName != null) {
|
||||
lastNameController.text = authResult.lastName!;
|
||||
}
|
||||
if (authResult.email != null) {
|
||||
emailController.text = authResult.email!;
|
||||
}
|
||||
if (authResult.phone != null) {
|
||||
final cleanPhone = authResult.phone!.replaceAll(RegExp(r'[^\d]'), '');
|
||||
final normalizedPhone = cleanPhone.startsWith('8') ? '7${cleanPhone.substring(1)}' : cleanPhone;
|
||||
final phoneWithCountryCode = normalizedPhone.startsWith('7') ? normalizedPhone : '7$normalizedPhone';
|
||||
final formattedPhone = maskFormatter.maskText('+$phoneWithCountryCode');
|
||||
phoneController.text = formattedPhone;
|
||||
}
|
||||
if (authResult.birthDate != null) {
|
||||
final formattedDate = _formatBirthDate(authResult.birthDate!);
|
||||
final maskedDate = maskFormatterForBirthday.maskText(formattedDate);
|
||||
dateOfBirthController.text = maskedDate;
|
||||
}
|
||||
if (authResult.gender != null) {
|
||||
isMaleSelected = authResult.gender == GenderEnum.male;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _formatBirthDate(String birthDate) {
|
||||
try {
|
||||
// Пытаемся распарсить различные форматы даты
|
||||
DateTime? date;
|
||||
|
||||
// Если дата в формате ISO (YYYY-MM-DD)
|
||||
if (birthDate.contains('-')) {
|
||||
date = DateTime.tryParse(birthDate);
|
||||
}
|
||||
// Если дата в формате DD.MM.YYYY
|
||||
else if (birthDate.contains('.')) {
|
||||
final parts = birthDate.split('.');
|
||||
if (parts.length == 3) {
|
||||
final day = int.tryParse(parts[0]);
|
||||
final month = int.tryParse(parts[1]);
|
||||
final year = int.tryParse(parts[2]);
|
||||
if (day != null && month != null && year != null) {
|
||||
date = DateTime(year, month, day);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (date != null) {
|
||||
// Форматируем в DD.MM.YYYY
|
||||
final day = date.day.toString().padLeft(2, '0');
|
||||
final month = date.month.toString().padLeft(2, '0');
|
||||
final year = date.year.toString();
|
||||
return '$day.$month.$year';
|
||||
}
|
||||
} catch (e) {
|
||||
// Если не удалось распарсить, возвращаем исходную строку
|
||||
}
|
||||
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
firstNameController.dispose();
|
||||
lastNameController.dispose();
|
||||
emailController.dispose();
|
||||
phoneController.dispose();
|
||||
dateOfBirthController.dispose();
|
||||
weightController.dispose();
|
||||
heightController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<OAuthCubit>(
|
||||
create: (context) => CubitRegistrations.sl<OAuthCubit>(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: BlocBuilder<OAuthCubit, OAuthState>(
|
||||
builder: (context, state) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Center(
|
||||
child: Text(
|
||||
'Регистрация',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1, color: Color(0xFF323F4B)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Имя'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(controller: firstNameController, hintText: 'Введите имя', onChanged: (newValue) {}),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Фамилия'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(controller: lastNameController, hintText: 'Введите фамилию', onChanged: (newValue) {}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text('Почта'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: emailController,
|
||||
textInputType: TextInputType.emailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
hintText: 'Введите email',
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (newValue) {},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text('Номер телефона'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
textInputAction: TextInputAction.done,
|
||||
hintText: '+7 (999) 999 99 99',
|
||||
controller: phoneController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [maskFormatter],
|
||||
onChanged: (newValue) {
|
||||
// Дополнительная обработка изменений номера телефона
|
||||
// Это поможет маске корректно работать при редактировании
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Дата рождения'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
textInputAction: TextInputAction.done,
|
||||
hintText: '01.01.2000',
|
||||
controller: dateOfBirthController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [maskFormatterForBirthday],
|
||||
onChanged: (newValue) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Пол'),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _genderButton('М', isMaleSelected)),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _genderButton('Ж', !isMaleSelected)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Вес'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(controller: weightController, hintText: 'Введите вес', onChanged: (newValue) {}),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 22,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Рост'),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(controller: heightController, hintText: 'Введите рост', onChanged: (newValue) {}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
const Center(
|
||||
child: Text(
|
||||
'Проверьте правильность заполненных\nданных. Пол, рост и вес требуются для\nрасчета вашей дневной нормы калорий',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: () => _handleRegistration(context),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(color: state is OAuthStateLoading ? Colors.grey : appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
|
||||
child: Center(
|
||||
child: state is OAuthStateLoading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text(
|
||||
'Зарегистрироваться',
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'Нажимая "Зарегистрироваться", Вы соглашаетесь\n',
|
||||
style: const TextStyle(fontSize: 16, color: Colors.black),
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: 'c ',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
if (!hidePlans) ...[
|
||||
TextSpan(
|
||||
text: ' Публичной офертой',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: appTheme.blueA400,
|
||||
decorationColor: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}oferta_bs.pdf')),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' и ',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
],
|
||||
TextSpan(
|
||||
text: 'Политикой конфиденциальности и защитой персональных данных',
|
||||
style: TextStyle(fontSize: 16, color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
|
||||
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/docs/privacy.pdf')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Показываем ошибки если есть
|
||||
if (state is OAuthStateError) ...[
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: Text(
|
||||
state.errorMessage,
|
||||
style: TextStyle(color: Colors.red.shade700),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
// Обработка успешной регистрации
|
||||
if (state is OAuthStateLoaded) ...[
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.green.shade200),
|
||||
),
|
||||
child: const Text(
|
||||
'Регистрация успешно завершена!',
|
||||
style: TextStyle(color: Colors.green),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// Автоматический переход на главный экран после успешной регистрации
|
||||
Builder(
|
||||
builder: (context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_reconnectWebSocket(context);
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _genderButton(String gender, bool isActive) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isMaleSelected = gender == 'М';
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF6F6F6),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: isActive ? Border.all(width: 2, color: appTheme.blueA400) : null,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
gender,
|
||||
style: TextStyle(color: isActive ? appTheme.blueA400 : const Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleRegistration(BuildContext context) {
|
||||
// Валидация полей
|
||||
if (firstNameController.text.isEmpty ||
|
||||
lastNameController.text.isEmpty ||
|
||||
emailController.text.isEmpty ||
|
||||
phoneController.text.isEmpty ||
|
||||
dateOfBirthController.text.isEmpty ||
|
||||
weightController.text.isEmpty ||
|
||||
heightController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Пожалуйста, заполните все поля')));
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсим числовые значения
|
||||
int? weight;
|
||||
int? height;
|
||||
try {
|
||||
weight = int.parse(weightController.text);
|
||||
height = int.parse(heightController.text);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Вес и рост должны быть числами')));
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем номер телефона от маски для отправки
|
||||
final cleanPhone = phoneController.text.replaceAll(RegExp(r'[^\d+]'), '');
|
||||
|
||||
// Вызываем OAuth регистрацию
|
||||
context.read<OAuthCubit>().getOAuthToken(
|
||||
provider: widget.provider ?? 'social',
|
||||
accessToken: widget.authResult?.accessToken ?? 'dummy_token',
|
||||
id: widget.authResult?.userId ?? 'dummy_id',
|
||||
refreshToken: widget.authResult?.refreshToken ?? 'dummy_refresh_token',
|
||||
firstName: firstNameController.text,
|
||||
lastName: lastNameController.text,
|
||||
email: emailController.text,
|
||||
phone: cleanPhone,
|
||||
dateOfBirth: dateOfBirthController.text,
|
||||
gender: isMaleSelected ? 'М' : 'Ж',
|
||||
weight: weight,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
@@ -40,17 +40,11 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Сканирование QR-кода'),
|
||||
centerTitle: true,
|
||||
),
|
||||
appBar: AppBar(title: const Text('Сканирование QR-кода'), centerTitle: true),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: QRView(
|
||||
key: qrKey,
|
||||
onQRViewCreated: _onQRViewCreated,
|
||||
),
|
||||
child: QRView(key: qrKey, onQRViewCreated: _onQRViewCreated),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -68,17 +62,9 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||
final scannedUrl = scanData.code;
|
||||
final uri = Uri.parse(scannedUrl ?? '');
|
||||
final scannedBaseUrl = (uri.hasAuthority) ? '${uri.scheme}://${uri.host}' : '';
|
||||
baseUrl = scannedBaseUrl;
|
||||
sl<Dio>().options.baseUrl = baseUrl;
|
||||
final response = await Dio().getUri(
|
||||
uri,
|
||||
options: Options(
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
),
|
||||
);
|
||||
sl<AppConfig>().setBaseUrl(scannedBaseUrl);
|
||||
sl<Dio>().options.baseUrl = scannedBaseUrl;
|
||||
final response = await Dio().getUri(uri, options: Options(headers: {'accept': 'application/json', 'content-type': 'application/json'}));
|
||||
final token = response.data['token'];
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
try {
|
||||
@@ -97,10 +83,7 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.go(
|
||||
'/feed-screen',
|
||||
extra: {'needToInitializeWebSocket': false},
|
||||
);
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
||||
showSuccessToast('Добро пожаловать!');
|
||||
} catch (e) {
|
||||
await controller.resumeCamera();
|
||||
|
||||
@@ -1,25 +1,79 @@
|
||||
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_login_request.dart';
|
||||
import 'package:eatvio_app/features/auth/models/token_model.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||
import 'package:eatvio_app/features/auth/models/social_exchange_request.dart';
|
||||
|
||||
class AuthRepository {
|
||||
final AuthService authService;
|
||||
|
||||
AuthRepository({required this.authService});
|
||||
|
||||
Future<TokenModel> getTokenByCredentials(
|
||||
String email,
|
||||
String password,
|
||||
) async {
|
||||
Future<TokenModel> getTokenByCredentials(String email, String password) async {
|
||||
try {
|
||||
return await authService.getTokenByCredentials(
|
||||
credentials: CredentialsModel(
|
||||
email: email,
|
||||
password: password,
|
||||
),
|
||||
credentials: CredentialsModel(email: email, password: password),
|
||||
);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<TokenModel> getTokenBySocial({required String provider, required String accessToken}) async {
|
||||
try {
|
||||
return await authService.getTokenBySocial(
|
||||
body: SocialExchangeRequest(provider: provider, accessToken: accessToken),
|
||||
);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<TokenModel> getOAuthLogin({required String provider, required String accessToken, required String refreshToken}) async {
|
||||
try {
|
||||
return await authService.getOAuthLogin(
|
||||
body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken),
|
||||
);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getOAuthToken({
|
||||
required String provider,
|
||||
required String accessToken,
|
||||
required String id,
|
||||
required String refreshToken,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required String email,
|
||||
required String phone,
|
||||
required String dateOfBirth,
|
||||
required String gender,
|
||||
required int weight,
|
||||
required int height,
|
||||
}) async {
|
||||
try {
|
||||
return (await authService.getOAuthToken(
|
||||
body: OAuthModel(
|
||||
provider: provider,
|
||||
accessToken: accessToken,
|
||||
id: id,
|
||||
refreshToken: refreshToken,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
dateOfBirth: dateOfBirth,
|
||||
gender: gender == 'М' ? 'male' : 'female',
|
||||
weight: weight,
|
||||
height: height,
|
||||
),
|
||||
))['token']!
|
||||
as String;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
267
lib/features/auth/sdk/social_auth_facade.dart
Normal file
267
lib/features/auth/sdk/social_auth_facade.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:eatvio_app/features/auth/models/yandex_profile_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/env_config.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:vk_id/vk_id.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class SocialAuthFacade {
|
||||
static Future<String?> signInWithVk() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return null;
|
||||
final result = await signInWithVkAndFetchProfile(context: context);
|
||||
return result?.accessToken;
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> signInWithVkAndFetchProfile({required BuildContext context}) async {
|
||||
final clientIdStr = Platform.isIOS ? EnvConfig.vkClientIdIos.trim() : EnvConfig.vkClientIdAndroid.trim();
|
||||
if (clientIdStr.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final controller = VkIDController(clID: int.parse(clientIdStr));
|
||||
final authUri = controller.generateAuthorizeLinkWithCodeVerifier(provider: VkProvider.vkID, scopes: [VkScope.email, VkScope.phone]);
|
||||
|
||||
final authUrl = authUri.value.toString();
|
||||
final codeVerifier = authUri.key;
|
||||
final redirectScheme = 'vk$clientIdStr://';
|
||||
|
||||
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUrl, redirectSchemePrefix: redirectScheme);
|
||||
if (redirectParams == null) {
|
||||
return null;
|
||||
}
|
||||
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> _parseVKAuthResult({
|
||||
required Map<String, String> redirectParams,
|
||||
required VkIDController controller,
|
||||
required String codeVerifier,
|
||||
}) async {
|
||||
final code = redirectParams['code'];
|
||||
final deviceId = redirectParams['device_id'];
|
||||
final state = redirectParams['state'];
|
||||
if (code == null || deviceId == null || state == null) {
|
||||
return null;
|
||||
}
|
||||
final authRes = await controller.retrieveOAuthToken(authorizationCode: code, deviceId: deviceId, codeVerifier: codeVerifier, state: state);
|
||||
final auth = authRes.result;
|
||||
if (auth == null) {
|
||||
var msg = "Retrieve OAuth token error";
|
||||
final err = authRes.error?.vkErr;
|
||||
if (err != null) {
|
||||
msg += ": ${err.error} - ${err.description}";
|
||||
}
|
||||
log(msg);
|
||||
return null;
|
||||
}
|
||||
final profileRes = await controller.getProfileInfo();
|
||||
final profile = profileRes.result;
|
||||
if (profile == null) {
|
||||
var msg = "Profile info load error";
|
||||
final err = profileRes.error?.vkErr;
|
||||
if (err != null) {
|
||||
msg += ": ${err.error} - ${err.description}";
|
||||
}
|
||||
log(msg);
|
||||
return null;
|
||||
}
|
||||
|
||||
return AuthResultEntity(
|
||||
accessToken: auth.accessToken,
|
||||
refreshToken: auth.refreshToken,
|
||||
userId: profile.userId.toString(),
|
||||
firstName: profile.firstName,
|
||||
lastName: profile.lastName,
|
||||
email: profile.email,
|
||||
phone: profile.phone,
|
||||
gender: GenderEnum.fromVkGender(profile.parsedSex),
|
||||
birthDate: profile.bDayDt?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> signInWithOkAndFetchProfile() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return null;
|
||||
return await _signInWithVkIdProvider(context: context, provider: VkProvider.okRu);
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> signInWithMailRuAndFetchProfile() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return null;
|
||||
return await _signInWithVkIdProvider(context: context, provider: VkProvider.mailRu);
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> signInWithYandex() async {
|
||||
final clientId = EnvConfig.yandexClientId.trim();
|
||||
final redirectUri = EnvConfig.yandexRedirectUri.trim();
|
||||
|
||||
if (clientId.isEmpty || redirectUri.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return null;
|
||||
|
||||
final authorizeUrl = Uri.https('oauth.yandex.ru', '/authorize', {
|
||||
'response_type': 'code',
|
||||
'client_id': clientId,
|
||||
'redirect_uri': redirectUri,
|
||||
'force_confirm': 'true',
|
||||
}).toString();
|
||||
|
||||
final redirectSchemePrefix = redirectUri;
|
||||
|
||||
final params = await _presentOAuthWebView(context: context, initialUrl: authorizeUrl, redirectSchemePrefix: redirectSchemePrefix);
|
||||
|
||||
if (params == null) return null;
|
||||
|
||||
final code = params['code'];
|
||||
if (code == null || code.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final tokens = await _getTokensFomYandex(code: code, clientId: clientId, clientSecret: EnvConfig.yandexClientSecret);
|
||||
if (tokens == null) {
|
||||
return null;
|
||||
}
|
||||
final response = await http.get(Uri.parse('https://login.yandex.ru/info?format=json'), headers: {'Authorization': 'OAuth ${tokens.accessToken}'});
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(response.body);
|
||||
final yandexProfileData = YandexProfileModel.fromJson(json);
|
||||
return AuthResultEntity(
|
||||
accessToken: tokens.accessToken,
|
||||
refreshToken: tokens.refreshToken,
|
||||
userId: yandexProfileData.id,
|
||||
firstName: yandexProfileData.firstName,
|
||||
lastName: yandexProfileData.lastName,
|
||||
email: yandexProfileData.defaultEmail,
|
||||
phone: yandexProfileData.defaultPhone.number,
|
||||
gender: GenderEnum.fromString(yandexProfileData.sex),
|
||||
birthDate: yandexProfileData.birthday,
|
||||
);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<({String accessToken, String refreshToken})?> _getTokensFomYandex({
|
||||
required String code,
|
||||
required String clientId,
|
||||
required String clientSecret,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('https://oauth.yandex.ru/token'),
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: {'grant_type': 'authorization_code', 'code': code, 'client_id': clientId, 'client_secret': clientSecret},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
log('Yandex token exchange failed: ${response.body}');
|
||||
return null;
|
||||
}
|
||||
|
||||
final tokens = jsonDecode(response.body);
|
||||
final accessToken = tokens['access_token'] as String;
|
||||
final refreshToken = tokens['refresh_token'] as String;
|
||||
return (accessToken: accessToken, refreshToken: refreshToken);
|
||||
}
|
||||
|
||||
static Future<AuthResultEntity?> _signInWithVkIdProvider({required BuildContext context, required VkProvider provider}) async {
|
||||
final clientIdStr = Platform.isIOS ? EnvConfig.vkClientIdIos.trim() : EnvConfig.vkClientIdAndroid.trim();
|
||||
if (clientIdStr.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final controller = VkIDController(clID: int.parse(clientIdStr));
|
||||
|
||||
final authUri = controller.generateAuthorizeLinkWithCodeVerifier(provider: provider, scopes: [VkScope.phone, VkScope.email,]);
|
||||
final codeVerifier = authUri.key;
|
||||
final authUrl = authUri.value.toString();
|
||||
final redirectScheme = 'vk$clientIdStr://';
|
||||
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUrl, redirectSchemePrefix: redirectScheme);
|
||||
if (redirectParams == null) return null;
|
||||
|
||||
final code = redirectParams['code'];
|
||||
final deviceId = redirectParams['device_id'];
|
||||
if (code == null || deviceId == null) return null;
|
||||
|
||||
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, String>?> _presentOAuthWebView({
|
||||
required BuildContext context,
|
||||
required String initialUrl,
|
||||
required String redirectSchemePrefix,
|
||||
}) async {
|
||||
final controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onNavigationRequest: (request) {
|
||||
final url = request.url;
|
||||
if (url.startsWith(redirectSchemePrefix)) {
|
||||
final uri = Uri.parse(url);
|
||||
final params = <String, String>{}..addAll(uri.queryParameters);
|
||||
if (uri.fragment.isNotEmpty) {
|
||||
final fragPairs = uri.fragment.split('&');
|
||||
for (final pair in fragPairs) {
|
||||
final eq = pair.indexOf('=');
|
||||
if (eq > 0 && eq < pair.length - 1) {
|
||||
final key = Uri.decodeComponent(pair.substring(0, eq));
|
||||
final value = Uri.decodeComponent(pair.substring(eq + 1));
|
||||
params[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Navigator.of(context, rootNavigator: true).pop(params);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
onPageStarted: (url) {},
|
||||
onPageFinished: (url) {},
|
||||
onWebResourceError: (error) {},
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await controller.loadRequest(Uri.parse(initialUrl));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (!context.mounted) return null;
|
||||
return showDialog<Map<String, String>>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) {
|
||||
const borderRadius = BorderRadius.all(Radius.circular(16));
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
shape: const RoundedRectangleBorder(borderRadius: borderRadius),
|
||||
elevation: 0,
|
||||
child: ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: SizedBox(height: 560, child: WebViewWidget(controller: controller)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/oauth_login_request.dart';
|
||||
import 'package:eatvio_app/features/auth/models/token_model.dart';
|
||||
import 'package:eatvio_app/features/auth/models/social_exchange_request.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'auth_service.g.dart';
|
||||
@@ -10,7 +13,14 @@ abstract class AuthService {
|
||||
factory AuthService(Dio dio, {String? baseUrl}) = _AuthService;
|
||||
|
||||
@POST('/api/login')
|
||||
Future<TokenModel> getTokenByCredentials({
|
||||
@Body() required CredentialsModel credentials,
|
||||
});
|
||||
Future<TokenModel> getTokenByCredentials({@Body() required CredentialsModel credentials});
|
||||
|
||||
@POST('/api/social/login')
|
||||
Future<TokenModel> getTokenBySocial({@Body() required SocialExchangeRequest body});
|
||||
|
||||
@POST('/api/oauth/callback')
|
||||
Future<Map<String, Object?>> getOAuthToken({@Body() required OAuthModel body});
|
||||
|
||||
@POST('/api/oauth/login')
|
||||
Future<TokenModel> getOAuthLogin({@Body() required OAuthLoginRequest body});
|
||||
}
|
||||
|
||||
@@ -18,4 +18,26 @@ class BlogPostCubit extends Cubit<BlogPostState> {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleLike() async {
|
||||
final current = state.blogPostModel;
|
||||
if (current == null) return;
|
||||
|
||||
// optimistic update
|
||||
final wasLiked = current.wasLikedByMe;
|
||||
final updated = current.copyWith(wasLikedByMe: !wasLiked, totalLikes: wasLiked ? current.totalLikes - 1 : current.totalLikes + 1);
|
||||
emit(state.copyWith(blogPostModel: updated));
|
||||
|
||||
try {
|
||||
if (wasLiked) {
|
||||
await feedRepository.unlikedBlogPost(id: current.id);
|
||||
} else {
|
||||
await feedRepository.likeBlogPost(id: current.id);
|
||||
}
|
||||
} catch (e, st) {
|
||||
// rollback on error
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(state.copyWith(blogPostModel: current));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
@@ -32,8 +32,6 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
final CommentsCubit commentsCubit = sl<CommentsCubit>();
|
||||
final textEditingController = TextEditingController();
|
||||
int? replyId;
|
||||
bool isLiked = false;
|
||||
int totalLikes = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -60,26 +58,30 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
builder: (BuildContext context, CommentsState state) {
|
||||
if (blogPostState.isLoading) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
GoRouter.of(context).pop();
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).go('/feed-screen');
|
||||
},
|
||||
),
|
||||
appBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
GoRouter.of(context).pop();
|
||||
return;
|
||||
}
|
||||
GoRouter.of(context).go('/feed-screen');
|
||||
},
|
||||
),
|
||||
body: const Loader());
|
||||
),
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false;
|
||||
totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0;
|
||||
final bool isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false;
|
||||
final int totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0;
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
bottomSheet: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
|
||||
decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1)))),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -114,24 +116,14 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black,
|
||||
height: 1.2,
|
||||
),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2),
|
||||
decoration: InputDecoration(
|
||||
isDense: false,
|
||||
// убираем true, чтобы padding учитывался корректно
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
// вертикальный паддинг увеличен
|
||||
hintText: 'Комментарий',
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.2,
|
||||
color: Color(0xFFAEAEB2),
|
||||
),
|
||||
hintStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -196,7 +188,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
id: widget.id,
|
||||
feedBlogPostModel: blogPostState.blogPostModel,
|
||||
userSlug: blogPostState.blogPostModel?.author.authorSlug ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
@@ -215,9 +207,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
foregroundImage: NetworkImage(
|
||||
baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? ''),
|
||||
),
|
||||
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? '')),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -242,30 +232,32 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Html(data: blogPostState.blogPostModel?.description.replaceAll('\n', '<br>'), style: {"*": Style(color: Colors.black87)}),
|
||||
child: Html(
|
||||
data: blogPostState.blogPostModel?.description.replaceAll('\n', '<br>'),
|
||||
style: {"*": Style(color: Colors.black87)},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
|
||||
CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}',
|
||||
),
|
||||
CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${blogPostState.blogPostModel?.image ?? ''}'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
_iconButton(
|
||||
icon: Icons.favorite_border_rounded,
|
||||
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
|
||||
text: totalLikes.toString(),
|
||||
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
||||
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
|
||||
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
isLiked = !isLiked;
|
||||
isLiked ? totalLikes++ : totalLikes--;
|
||||
});
|
||||
isLiked ? await context.read<FeedCubit>().likePost(widget.id) : await context.read<FeedCubit>().unlikedPost(widget.id);
|
||||
sl<FeedCubit>().updateFeedBlogPostList(blogPostState.blogPostModel, isLiked);
|
||||
await context.read<BlogPostCubit>().toggleLike();
|
||||
// ignore: use_build_context_synchronously
|
||||
final updated = context.read<BlogPostCubit>().state.blogPostModel;
|
||||
if (updated != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
context.read<FeedCubit>().updateFeedBlogPostList(updated, updated.wasLikedByMe);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -275,7 +267,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.id}'),
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/blog_post/${widget.id}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
@@ -284,8 +276,10 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
const Spacer(),
|
||||
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
|
||||
const SizedBox(width: 4),
|
||||
Text(blogPostState.blogPostModel?.totalViews.toString() ?? '',
|
||||
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)),
|
||||
Text(
|
||||
blogPostState.blogPostModel?.totalViews.toString() ?? '',
|
||||
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -332,7 +326,11 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
children: [
|
||||
Icon(icon, size: 20, color: iconColor ?? appTheme.gray600),
|
||||
if (text != null) const SizedBox(width: 8),
|
||||
if (text != null) Text(text, style: TextStyle(color: textColor ?? appTheme.gray600, fontWeight: FontWeight.w500)),
|
||||
if (text != null)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(color: textColor ?? appTheme.gray600, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,61 +1,34 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/cubit/base_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
|
||||
final BlogPostRepository blogPostRepository;
|
||||
|
||||
BlogPostCreateCubit({
|
||||
required this.blogPostRepository,
|
||||
}) : super(const BlogPostCreateState());
|
||||
BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState());
|
||||
|
||||
Future<bool> createBlogPost({
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
String? description,
|
||||
XFile? image,
|
||||
}) async {
|
||||
Future<bool> createBlogPost({String? title, String? videoUrl, String? description, XFile? image}) async {
|
||||
try {
|
||||
emit(const BlogPostCreateState.loading());
|
||||
await blogPostRepository.createBlogPost(
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
);
|
||||
await blogPostRepository.createBlogPost(title, videoUrl, description, image);
|
||||
emit(const BlogPostCreateState.loaded());
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
} catch (error, stackTrace) {
|
||||
handleError(error, stackTrace);
|
||||
emit(const BlogPostCreateState.loaded());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> updateBlogPost({
|
||||
required int id,
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
String? description,
|
||||
XFile? image,
|
||||
String? oldImageUrl,
|
||||
}) async {
|
||||
Future<bool> updateBlogPost({required int id, String? title, String? videoUrl, String? description, XFile? image, String? oldImageUrl}) async {
|
||||
try {
|
||||
emit(const BlogPostCreateState.loading());
|
||||
await blogPostRepository.updateBlogPost(
|
||||
id,
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
oldImageUrl,
|
||||
);
|
||||
await blogPostRepository.updateBlogPost(id, title, videoUrl, description, image, oldImageUrl);
|
||||
emit(const BlogPostCreateState.loaded());
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
} catch (error, stackTrace) {
|
||||
handleError(error, stackTrace);
|
||||
emit(const BlogPostCreateState.loaded());
|
||||
return false;
|
||||
}
|
||||
@@ -65,8 +38,8 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
try {
|
||||
await blogPostRepository.deleteBlogPost(id);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
} catch (error, stackTrace) {
|
||||
handleError(error, stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
@@ -304,7 +304,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Image(
|
||||
image: NetworkImage('$baseUrl/storage/${widget.feedBlogPostModel!.image}'),
|
||||
image: NetworkImage('${sl<AppConfig>().baseUrl}/storage/${widget.feedBlogPostModel!.image}'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
@@ -36,7 +37,7 @@ class BlogPostRepository {
|
||||
Future<XFile?> downloadImage(String imageUrl) async {
|
||||
try {
|
||||
// Загрузка изображения
|
||||
final response = await http.get(Uri.parse('$baseUrl/storage/$imageUrl'));
|
||||
final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Получение временной директории
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
@@ -102,7 +103,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||
label: '',
|
||||
icon: CircleAvatar(
|
||||
foregroundImage: NetworkImage(
|
||||
widget.profileModel.profilePhotoUrl.contains('https') ? widget.profileModel.profilePhotoUrl : baseUrl + widget.profileModel.profilePhotoUrl,
|
||||
widget.profileModel.profilePhotoUrl.contains('https') ? widget.profileModel.profilePhotoUrl : sl<AppConfig>().baseUrl + widget.profileModel.profilePhotoUrl,
|
||||
),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
radius: 16,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -88,7 +89,7 @@ class _PhotoViewScreenState extends State<PhotoViewScreen> {
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(
|
||||
widget.imageUrls[index].replaceAll('$baseUrl/storage/chat_files/', ''),
|
||||
widget.imageUrls[index].replaceAll('${sl<AppConfig>().baseUrl}/storage/chat_files/', ''),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
@@ -47,7 +48,7 @@ class _OrderCardState extends State<OrderCard> {
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.network(
|
||||
'$baseUrl/storage/${widget.plan.image}',
|
||||
'${sl<AppConfig>().baseUrl}/storage/${widget.plan.image}',
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -49,7 +51,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: TextButton(
|
||||
onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '$baseUrl/docs/privacy.pdf' : '$baseUrl/oferta_bs.pdf')),
|
||||
onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '${sl<AppConfig>().baseUrl}/docs/privacy.pdf' : '${sl<AppConfig>().baseUrl}/oferta_bs.pdf')),
|
||||
child: Text(
|
||||
hidePlans ? 'Политика конфиденциальности и пользовательское соглашение' : 'Договор офёрты',
|
||||
style: const TextStyle(height: 1, fontSize: 16, color: Color(0xFF323F4B), decoration: TextDecoration.underline),
|
||||
|
||||
@@ -13,6 +13,7 @@ abstract class StatisticsModel with _$StatisticsModel {
|
||||
@JsonKey(name: 'count_diets') int? dietsCount,
|
||||
@JsonKey(name: 'count_consultants') int? consultationsCount,
|
||||
@JsonKey(name: 'count_products_nutritional_values') int? qwe,
|
||||
@JsonKey(name: 'referral_link') String? refLink,
|
||||
}) = _StatisticsModel;
|
||||
|
||||
factory StatisticsModel.fromJson(Map<String, dynamic> json) => _$StatisticsModelFromJson(json);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:eatvio_app/features/consultant_rating/presentation/widgets/ratin
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ConsultantRatingScreen extends StatelessWidget {
|
||||
@@ -53,8 +55,8 @@ class ConsultantRatingPageContent extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const RatingWidget(
|
||||
rating: 1058,
|
||||
RatingWidget(
|
||||
rating: state.absoluteRating!,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
@@ -67,7 +69,10 @@ class ConsultantRatingPageContent extends StatelessWidget {
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: state.statistics?.refLink ?? ''));
|
||||
showInfoToast(s.link_was_copied);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -2,17 +2,23 @@ import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
||||
final ConsultantRatingRepository repository;
|
||||
ConsultantRatingCubit(this.repository) : super(ConsultantRatingState());
|
||||
final FeedRepository feedRepository;
|
||||
final ProfileRepository profileRepository;
|
||||
|
||||
ConsultantRatingCubit({required this.repository, required this.feedRepository, required this.profileRepository}) : super(ConsultantRatingState());
|
||||
|
||||
Future<void> getConsultantStatistics({String? date}) async {
|
||||
try {
|
||||
emit(state.copyWith(isStatisticsLoading: true));
|
||||
final statistics = await repository.getConsultantStatistics(date: date);
|
||||
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null));
|
||||
final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name);
|
||||
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores));
|
||||
} catch (e, st) {
|
||||
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
|
||||
log(e.toString(), stackTrace: st);
|
||||
|
||||
@@ -12,5 +12,6 @@ abstract class ConsultantRatingState with _$ConsultantRatingState {
|
||||
@Default(null) StatisticsModel? statistics,
|
||||
@Default(null) String? error,
|
||||
@Default(<RatingModel>[]) List<RatingModel> ratings,
|
||||
@Default(0) int? absoluteRating,
|
||||
}) = _ConsultantRatingState;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class StatisticWidget extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
|
||||
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.added_products_in_glossary)),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -63,7 +65,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(radius: 30, backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl)),
|
||||
CircleAvatar(radius: 30, backgroundImage: NetworkImage(widget.consultationsModel.userProfilePhotoUrl)),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -284,6 +286,19 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
// Кнопка получения выплаты
|
||||
if (widget.consultationsModel.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[
|
||||
const SizedBox(height: 16),
|
||||
CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () async {
|
||||
GoRouter.of(context).push(CardSettingsScreen.path, extra: widget.consultationsModel.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,8 +2,10 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:developer';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
@@ -53,7 +55,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
name: diet.name,
|
||||
duration: diet.duration,
|
||||
type: type,
|
||||
imageUrl: '$baseUrl/storage/${diet.image}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${diet.image}',
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart' as i;
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
@@ -187,13 +188,13 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
}
|
||||
|
||||
Future<void> recipeToEntity(Recipe recipe) async {
|
||||
final file = await downloadImageAsFile('$baseUrl/storage/${recipe.image}');
|
||||
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}');
|
||||
|
||||
final List<StepEntity> steps = await Future.wait(
|
||||
recipe.steps!.map(
|
||||
(e) async => StepEntity(
|
||||
description: e.description,
|
||||
image: e.image.isEmpty ? null : await downloadImageAsFile('$baseUrl/storage/${e.image}'),
|
||||
image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
@@ -84,7 +84,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
baseUrlForTest = baseUrl;
|
||||
baseUrlForTest = sl<AppConfig>().baseUrl;
|
||||
});
|
||||
testsStates = List.generate(testCases.length, (index) => TestState.notRun);
|
||||
}
|
||||
@@ -111,8 +111,8 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
baseUrlForTest = controller.text;
|
||||
baseUrl = baseUrlForTest.toLowerCase();
|
||||
sl<Dio>().options.baseUrl = baseUrl;
|
||||
sl<AppConfig>().setBaseUrl(baseUrlForTest.toLowerCase());
|
||||
sl<Dio>().options.baseUrl = baseUrlForTest.toLowerCase();
|
||||
controller.clear();
|
||||
});
|
||||
},
|
||||
@@ -122,23 +122,23 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
CustomOutlinedButton(
|
||||
text: baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url',
|
||||
text: sl<AppConfig>().baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (baseUrl == EnvConfig.baseUrl) {
|
||||
if (sl<AppConfig>().baseUrl == EnvConfig.baseUrl) {
|
||||
baseUrlForTest = EnvConfig.devBaseUrl;
|
||||
} else {
|
||||
baseUrlForTest = EnvConfig.baseUrl;
|
||||
}
|
||||
baseUrl = baseUrlForTest.toLowerCase();
|
||||
sl<Dio>().options.baseUrl = baseUrl;
|
||||
sl<AppConfig>().setBaseUrl(baseUrlForTest.toLowerCase());
|
||||
sl<Dio>().options.baseUrl = baseUrlForTest.toLowerCase();
|
||||
controller.clear();
|
||||
});
|
||||
},
|
||||
buttonStyle: const ButtonStyle(),
|
||||
),
|
||||
SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10),
|
||||
if (baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase())
|
||||
SizedBox(height: baseUrlForTest != sl<AppConfig>().baseUrl ? 40 : 10),
|
||||
if (sl<AppConfig>().baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase())
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: testCases.length,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
@@ -6,77 +7,84 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ChosenDietWidget extends StatelessWidget {
|
||||
const ChosenDietWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DiaryCubit, DiaryState>(builder: (context, state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
if (state.diary.typeOfDiary.hasChosenDiet) {
|
||||
return ChosenDietWidgetContent(
|
||||
currentDietDay: state.diary.dayOfChosenDiet ?? 0,
|
||||
dietName: state.diary.diet?.name ?? '',
|
||||
dietDuration: state.diary.diet?.duration ?? 0,
|
||||
);
|
||||
return BlocBuilder<DiaryCubit, DiaryState>(
|
||||
builder: (context, state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
if (state.diary.typeOfDiary.hasChosenDiet) {
|
||||
return ChosenDietWidgetContent(
|
||||
currentDietDay: state.diary.dayOfChosenDiet ?? 0,
|
||||
dietName: state.diary.diet?.name ?? '',
|
||||
dietDuration: state.diary.diet?.duration ?? 0,
|
||||
dietId: state.diary.diet?.id ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
||||
if (state.diary.typeOfDiary.hasChosenDiet) {
|
||||
return Column(
|
||||
children: [
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 16),
|
||||
ChosenDietWidgetContent(
|
||||
currentDietDay: state.diary.dayOfChosenDiet ?? 0,
|
||||
dietName: state.diary.diet?.name ?? '',
|
||||
dietDuration: state.diary.diet?.duration ?? 0,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
||||
if (state.diary.typeOfDiary.hasChosenDiet) {
|
||||
return Column(
|
||||
children: [
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 16),
|
||||
ChosenDietWidgetContent(
|
||||
currentDietDay: state.diary.dayOfChosenDiet ?? 0,
|
||||
dietName: state.diary.diet?.name ?? '',
|
||||
dietDuration: state.diary.diet?.duration ?? 0,
|
||||
dietId: state.diary.diet?.id ?? 0,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChosenDietWidgetContent extends StatelessWidget {
|
||||
const ChosenDietWidgetContent({
|
||||
super.key,
|
||||
required this.currentDietDay,
|
||||
required this.dietName,
|
||||
required this.dietDuration,
|
||||
});
|
||||
const ChosenDietWidgetContent({super.key, required this.currentDietDay, required this.dietName, required this.dietDuration, required this.dietId});
|
||||
final int currentDietDay;
|
||||
final String dietName;
|
||||
final int dietDuration;
|
||||
final int dietId;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.4,
|
||||
child: Text(dietName, style: TextStyles.textLMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
decoration: BoxDecoration(color: appTheme.dividerColor, borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
child: Text('${s.day} $currentDietDay из $dietDuration', style: TextStyles.textMSemibold.copyWith(color: appTheme.gray)),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
)
|
||||
],
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
GoRouter.of(context).push(RouteConstants.aboutDietScreen, extra: {'id': dietId, 'navigateToComments': false});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.4,
|
||||
child: Text(
|
||||
dietName,
|
||||
style: TextStyles.textLMedium.copyWith(color: appTheme.black, decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
decoration: BoxDecoration(color: appTheme.dividerColor, borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
child: Text('${s.day} $currentDietDay из $dietDuration', style: TextStyles.textMSemibold.copyWith(color: appTheme.gray)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,12 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
builder: (BuildContext context, DiaryState state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
final selectedIndex = state.selectedMeal - 1;
|
||||
if (isFirstFetch[selectedIndex]) {
|
||||
currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].mood.toDouble() + 1;
|
||||
isFirstFetch[selectedIndex] = false;
|
||||
final meals = state.diary.meals;
|
||||
if (selectedIndex >= 0 && selectedIndex < meals.length) {
|
||||
if (isFirstFetch[selectedIndex]) {
|
||||
currentSelectedRating[selectedIndex] = meals[selectedIndex].mood.toDouble() + 1;
|
||||
isFirstFetch[selectedIndex] = false;
|
||||
}
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
@@ -35,21 +38,17 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
Text(
|
||||
state.isConsultantView ? 'Настроение пользователя после приема пищи' : 'Ваше настроение после приёма пищи',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/mood_1.svg',
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/mood_1.svg', height: 32, width: 32),
|
||||
RatingBar.builder(
|
||||
ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented,
|
||||
initialRating: state.diary.meals[selectedIndex].mood.toDouble() + 1,
|
||||
initialRating: (selectedIndex >= 0 && selectedIndex < state.diary.meals.length)
|
||||
? state.diary.meals[selectedIndex].mood.toDouble() + 1
|
||||
: 3,
|
||||
itemCount: 6,
|
||||
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
|
||||
updateOnDrag: true,
|
||||
@@ -63,20 +62,11 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFC82222),
|
||||
border: currentSelectedRating[selectedIndex] == 1
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFFAA3333),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFFAA3333), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 1
|
||||
? const BorderRadius.horizontal(
|
||||
left: Radius.circular(8),
|
||||
right: Radius.circular(2),
|
||||
)
|
||||
: const BorderRadius.horizontal(
|
||||
left: Radius.circular(8),
|
||||
),
|
||||
? const BorderRadius.horizontal(left: Radius.circular(8), right: Radius.circular(2))
|
||||
: const BorderRadius.horizontal(left: Radius.circular(8)),
|
||||
),
|
||||
);
|
||||
case 1:
|
||||
@@ -87,11 +77,7 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFF3E3E),
|
||||
border: currentSelectedRating[selectedIndex] == 2
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF550000),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF550000), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -104,11 +90,7 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8C926),
|
||||
border: currentSelectedRating[selectedIndex] == 3
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF8C5D00),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF8C5D00), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -121,11 +103,7 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF3BF),
|
||||
border: currentSelectedRating[selectedIndex] == 4
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFFB5911B),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFFB5911B), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -138,11 +116,7 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF62E833),
|
||||
border: currentSelectedRating[selectedIndex] == 5
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF276918),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF276918), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -155,20 +129,11 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4DCE20),
|
||||
border: currentSelectedRating[selectedIndex] == 6
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF184D12),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF184D12), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 6
|
||||
? const BorderRadius.horizontal(
|
||||
left: Radius.circular(2),
|
||||
right: Radius.circular(8),
|
||||
)
|
||||
: const BorderRadius.horizontal(
|
||||
right: Radius.circular(8),
|
||||
),
|
||||
? const BorderRadius.horizontal(left: Radius.circular(2), right: Radius.circular(8))
|
||||
: const BorderRadius.horizontal(right: Radius.circular(8)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -177,20 +142,12 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
|
||||
setState(() {
|
||||
currentSelectedRating[selectedIndex] = value;
|
||||
});
|
||||
EasyDebounce.debounce(
|
||||
'change-satiety',
|
||||
const Duration(milliseconds: 250),
|
||||
() {
|
||||
context.read<DiaryCubit>().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1);
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('change-satiety', const Duration(milliseconds: 250), () {
|
||||
context.read<DiaryCubit>().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1);
|
||||
});
|
||||
},
|
||||
),
|
||||
SvgPicture.asset(
|
||||
'assets/icons/mood_2.svg',
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/mood_2.svg', height: 32, width: 32),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||
@@ -139,7 +140,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path}',
|
||||
),
|
||||
),
|
||||
if (!state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented)
|
||||
@@ -211,7 +212,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
|
||||
height: 52,
|
||||
width: 52,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${state.diary.meals[mealIndex].photos[i].path}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.diary.meals[mealIndex].photos[i].path}',
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -23,9 +23,12 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
builder: (BuildContext context, DiaryState state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
final selectedIndex = state.selectedMeal - 1;
|
||||
if (isFirstFetch[selectedIndex]) {
|
||||
currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].satiety.toDouble() + 1;
|
||||
isFirstFetch[selectedIndex] = false;
|
||||
final meals = state.diary.meals;
|
||||
if (selectedIndex >= 0 && selectedIndex < meals.length) {
|
||||
if (isFirstFetch[selectedIndex]) {
|
||||
currentSelectedRating[selectedIndex] = meals[selectedIndex].satiety.toDouble() + 1;
|
||||
isFirstFetch[selectedIndex] = false;
|
||||
}
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
@@ -35,21 +38,17 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
Text(
|
||||
state.isConsultantView ? 'Сытость пользователя после приема пищи' : 'Ваша сытость после приёма пищи',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/satiety_emoji_left.svg',
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/satiety_emoji_left.svg', height: 32, width: 32),
|
||||
RatingBar.builder(
|
||||
ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented,
|
||||
initialRating: state.diary.meals[selectedIndex].satiety.toDouble() + 1,
|
||||
initialRating: (selectedIndex >= 0 && selectedIndex < state.diary.meals.length)
|
||||
? state.diary.meals[selectedIndex].satiety.toDouble() + 1
|
||||
: 3,
|
||||
itemCount: 6,
|
||||
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
|
||||
updateOnDrag: true,
|
||||
@@ -63,20 +62,11 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFC82222),
|
||||
border: currentSelectedRating[selectedIndex] == 1
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFFAA3333),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFFAA3333), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 1
|
||||
? const BorderRadius.horizontal(
|
||||
left: Radius.circular(8),
|
||||
right: Radius.circular(2),
|
||||
)
|
||||
: const BorderRadius.horizontal(
|
||||
left: Radius.circular(8),
|
||||
),
|
||||
? const BorderRadius.horizontal(left: Radius.circular(8), right: Radius.circular(2))
|
||||
: const BorderRadius.horizontal(left: Radius.circular(8)),
|
||||
),
|
||||
);
|
||||
case 1:
|
||||
@@ -87,11 +77,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFF3E3E),
|
||||
border: currentSelectedRating[selectedIndex] == 2
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF550000),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF550000), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -104,11 +90,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8C926),
|
||||
border: currentSelectedRating[selectedIndex] == 3
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF8C5D00),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF8C5D00), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -121,11 +103,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF3BF),
|
||||
border: currentSelectedRating[selectedIndex] == 4
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFFB5911B),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFFB5911B), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -138,11 +116,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF62E833),
|
||||
border: currentSelectedRating[selectedIndex] == 5
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF276918),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF276918), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null,
|
||||
),
|
||||
@@ -155,20 +129,11 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4DCE20),
|
||||
border: currentSelectedRating[selectedIndex] == 6
|
||||
? Border.all(
|
||||
width: 2,
|
||||
color: const Color(0xFF184D12),
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
)
|
||||
? Border.all(width: 2, color: const Color(0xFF184D12), strokeAlign: BorderSide.strokeAlignOutside)
|
||||
: null,
|
||||
borderRadius: currentSelectedRating[selectedIndex] == 6
|
||||
? const BorderRadius.horizontal(
|
||||
left: Radius.circular(2),
|
||||
right: Radius.circular(8),
|
||||
)
|
||||
: const BorderRadius.horizontal(
|
||||
right: Radius.circular(8),
|
||||
),
|
||||
? const BorderRadius.horizontal(left: Radius.circular(2), right: Radius.circular(8))
|
||||
: const BorderRadius.horizontal(right: Radius.circular(8)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -178,20 +143,12 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
|
||||
setState(() {
|
||||
currentSelectedRating[selectedIndex] = value;
|
||||
});
|
||||
EasyDebounce.debounce(
|
||||
'change-satiety',
|
||||
const Duration(milliseconds: 250),
|
||||
() {
|
||||
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1);
|
||||
},
|
||||
);
|
||||
EasyDebounce.debounce('change-satiety', const Duration(milliseconds: 250), () {
|
||||
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1);
|
||||
});
|
||||
},
|
||||
),
|
||||
SvgPicture.asset(
|
||||
'assets/icons/satiety_emoji_right.svg',
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/satiety_emoji_right.svg', height: 32, width: 32),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -82,7 +82,53 @@ class DiaryRepository {
|
||||
}
|
||||
|
||||
Future<DiaryDayModel> getDiaryDay(int diaryId) async {
|
||||
return (await diaryService.getDiaryDay(diary: diaryId)).diary;
|
||||
// Align with getDiaryByDate: enrich portions with glossary nutritional info
|
||||
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
|
||||
final DiaryDayModel diaryDay = diaryWrapper.diary;
|
||||
Set<int> ids = {};
|
||||
for (var meal in diaryDay.meals) {
|
||||
for (var portion in meal.portions) {
|
||||
if (portion.productNutritionalValueId != null) {
|
||||
ids.add(portion.productNutritionalValueId!);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ids.isNotEmpty) {
|
||||
final glossary = await getGlossariesIndex(ids: ids.toList());
|
||||
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
|
||||
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
|
||||
final meal = updatedMeals[mealIndex];
|
||||
final updatedPortions = meal.portions.map((portion) {
|
||||
if (portion.productNutritionalValueId != null) {
|
||||
try {
|
||||
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
|
||||
return portion.copyWith(
|
||||
productCaloriesInfo: ProductCaloriesInfoModel(
|
||||
id: glossaryItem.id,
|
||||
productName: glossaryItem.name,
|
||||
calories: glossaryItem.energyOn100Gr,
|
||||
proteins: glossaryItem.proteinsOn100Gr,
|
||||
fats: glossaryItem.fatsOn100Gr,
|
||||
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
||||
energyOnPortion: glossaryItem.energyOnPortion,
|
||||
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
||||
fatsOnPortion: glossaryItem.fatsOnPortion,
|
||||
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return portion;
|
||||
}
|
||||
}
|
||||
return portion;
|
||||
}).toList();
|
||||
|
||||
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
|
||||
}
|
||||
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
|
||||
return updatedDiaryDay;
|
||||
}
|
||||
return diaryDay;
|
||||
}
|
||||
|
||||
Future<NutritionDataModel> getNutritionData(int index) async {
|
||||
|
||||
@@ -19,9 +19,14 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
String? userStartedDietName;
|
||||
List<int> _selectedIndexes = [];
|
||||
|
||||
Future<void> getDiet(String slug, {int? index}) async {
|
||||
Future<void> getDiet({String? slug, int? id, int? index}) async {
|
||||
try {
|
||||
final diet = await dietsRepository.getDietBySlug(slug);
|
||||
final Diet diet;
|
||||
if (slug != null) {
|
||||
diet = await dietsRepository.getDietBySlug(slug);
|
||||
} else {
|
||||
diet = await dietsRepository.getDiet(id!);
|
||||
}
|
||||
if (index != null) {
|
||||
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
|
||||
}
|
||||
@@ -32,10 +37,16 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initDietPage(String slug) async {
|
||||
Future<void> initDietPage({String? slug, int? id}) async {
|
||||
try {
|
||||
emit(const AboutDietState.loading());
|
||||
final diet = await dietsRepository.getDietBySlug(slug);
|
||||
final Diet diet;
|
||||
if (slug != null) {
|
||||
diet = await dietsRepository.getDietBySlug(slug);
|
||||
} else {
|
||||
diet = await dietsRepository.getDiet(id!);
|
||||
}
|
||||
|
||||
final chosenDiets = await profileRepository.getChosenDiets();
|
||||
final lastChosenDiet = chosenDiets.firstOrNull;
|
||||
if (lastChosenDiet != null && ['inited', 'started'].contains(lastChosenDiet.state)) {
|
||||
@@ -50,12 +61,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
}
|
||||
}
|
||||
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
|
||||
emit(AboutDietState.loaded(
|
||||
diet: diet,
|
||||
isStarted: isCurrentDietStarted,
|
||||
hasStartedDiet: hasStartedDiet,
|
||||
selectedIndexes: _selectedIndexes,
|
||||
));
|
||||
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
emit(AboutDietState.error(message: e.toString()));
|
||||
@@ -68,41 +74,30 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
|
||||
if (currentDiet.wasLikedByMe) {
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
totalLikes: currentDiet.totalLikes - 1,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
final updatedDiet = currentDiet.copyWith(totalLikes: currentDiet.totalLikes - 1, wasLikedByMe: false);
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||
} else {
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
totalLikes: currentDiet.totalLikes + 1,
|
||||
wasLikedByMe: true,
|
||||
);
|
||||
final updatedDiet = currentDiet.copyWith(totalLikes: currentDiet.totalLikes + 1, wasLikedByMe: true);
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||
}
|
||||
|
||||
EasyDebounce.debounce(
|
||||
'like_diet',
|
||||
const Duration(seconds: 1),
|
||||
() async {
|
||||
try {
|
||||
await dietsRepository.likeDiet(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository
|
||||
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async {
|
||||
try {
|
||||
await dietsRepository.likeDiet(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
},
|
||||
);
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addToFavorite(int id, int? dietIndex) async {
|
||||
@@ -110,30 +105,24 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
isFavorite: true,
|
||||
);
|
||||
final updatedDiet = currentDiet.copyWith(isFavorite: true);
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_diet_to_favorites',
|
||||
const Duration(seconds: 1),
|
||||
() async {
|
||||
try {
|
||||
await dietsRepository.addToFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
||||
try {
|
||||
await dietsRepository.addToFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
},
|
||||
);
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> removeFromFavorite(int id, int? dietIndex) async {
|
||||
@@ -141,30 +130,24 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
isFavorite: false,
|
||||
);
|
||||
final updatedDiet = currentDiet.copyWith(isFavorite: false);
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_diet_to_favorites',
|
||||
const Duration(seconds: 1),
|
||||
() async {
|
||||
try {
|
||||
await dietsRepository.removeFromFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
|
||||
try {
|
||||
await dietsRepository.removeFromFavorite(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
},
|
||||
);
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> startDiet(DateTime date) async {
|
||||
@@ -199,13 +182,13 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
void clearStartedDietsState() {
|
||||
if (state is! DietStateLoaded) return;
|
||||
final diet = (state as DietStateLoaded).diet;
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet));
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: diet));
|
||||
}
|
||||
|
||||
void emitCurrentStartedDietsState() {
|
||||
if (state is! DietStateLoaded) return;
|
||||
final diet = (state as DietStateLoaded).diet;
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet, isStarted: true));
|
||||
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes, diet: diet, isStarted: true));
|
||||
}
|
||||
|
||||
Future<Diet?> getDietBySlug(String slug) async {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
@@ -35,11 +35,12 @@ import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AboutDietScreen extends StatefulWidget {
|
||||
final String slug;
|
||||
final String? slug;
|
||||
final int? id;
|
||||
final int? index;
|
||||
final bool navigateToComments;
|
||||
|
||||
const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug});
|
||||
const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug, required this.id});
|
||||
|
||||
@override
|
||||
State<AboutDietScreen> createState() => _AboutDietScreenState();
|
||||
@@ -79,7 +80,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
final s = S.of(context);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => dietsCubit..initDietPage(widget.slug),
|
||||
create: (context) => dietsCubit..initDietPage(slug: widget.slug, id: widget.id),
|
||||
child: BlocProvider<CommentsCubit>(
|
||||
create: (BuildContext context) => commentsCubit,
|
||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
||||
@@ -283,7 +284,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
replyId = null;
|
||||
});
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
dietsCubit.getDiet(dietState.diet.slug, index: widget.index);
|
||||
dietsCubit.getDiet(slug: dietState.diet.slug, index: widget.index);
|
||||
});
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
@@ -353,7 +354,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
Stack(
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${dietState.diet.image}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${dietState.diet.image}',
|
||||
height: 300,
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
fit: BoxFit.fill,
|
||||
@@ -526,7 +527,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
),
|
||||
),
|
||||
body: DataErrorWidget(
|
||||
onReload: () => context.read<AboutDietCubit>().getDiet(widget.slug, index: widget.index),
|
||||
onReload: () => context.read<AboutDietCubit>().getDiet(slug: widget.slug, id: widget.id, index: widget.index),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
@@ -37,45 +38,25 @@ class AuthorCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(baseUrl + author.authorProfileIcon),
|
||||
image: NetworkImage(
|
||||
author.authorProfileIcon.contains('https') ? author.authorProfileIcon : sl<AppConfig>().baseUrl + author.authorProfileIcon,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
author.authorName,
|
||||
style: TextStyles.textLSemibold.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: titleStyle ?? TextStyles.textSSemibold.copyWith(
|
||||
color: appTheme.gray,
|
||||
),
|
||||
),
|
||||
subtitle: Text(author.authorName, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
|
||||
title: Text(title, style: titleStyle ?? TextStyles.textSSemibold.copyWith(color: appTheme.gray)),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Консультант',
|
||||
style: TextStyles.textMMedium.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
Text('Консультант', style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(width: 10),
|
||||
if (author.authorPlaceInRating != null)
|
||||
InfoBouble(
|
||||
radius: 12,
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
children: [
|
||||
Text(
|
||||
'${author.authorPlaceInRating}-й в рейтинге',
|
||||
style: TextStyles.textMMediumAuto.copyWith(
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
],
|
||||
children: [Text('${author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400))],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -88,18 +69,8 @@ class AuthorCard extends StatelessWidget {
|
||||
child: Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
'assets/icons/arrow.svg',
|
||||
width: 12,
|
||||
height: 12,
|
||||
fit: BoxFit.none,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(color: appTheme.whiteA700, borderRadius: BorderRadius.circular(8)),
|
||||
child: Center(child: SvgPicture.asset('assets/icons/arrow.svg', width: 12, height: 12, fit: BoxFit.none)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
@@ -35,7 +36,7 @@ class MoreOptions extends StatelessWidget {
|
||||
onTap: disableActions == true
|
||||
? null
|
||||
: () {
|
||||
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
|
||||
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
|
||||
showInfoToast(s.link_was_copied);
|
||||
},
|
||||
child: Row(
|
||||
@@ -62,7 +63,7 @@ class MoreOptions extends StatelessWidget {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('$baseUrl/ru/$path',),
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/$path',),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,13 +15,12 @@ class FeedCubit extends Cubit<FeedState> {
|
||||
FeedCubit({required this.feedRepository}) : super(const FeedState.loading());
|
||||
|
||||
void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) {
|
||||
if (feedBlogPostModel == null || isClosed) return;
|
||||
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
||||
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
|
||||
feedBlogPostList[index] = feedBlogPostModel.copyWith(
|
||||
wasLikedByMe: wasLikedByMe,
|
||||
totalLikes: wasLikedByMe == false ? feedBlogPostList[index].totalLikes - 1 : feedBlogPostList[index].totalLikes + 1,
|
||||
);
|
||||
if (feedBlogPostModel == null || isClosed || state is! FeedStateLoaded) return;
|
||||
final List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
||||
final int index = feedBlogPostList.indexWhere((e) => e.id == feedBlogPostModel.id);
|
||||
if (index == -1) return;
|
||||
final current = feedBlogPostList[index];
|
||||
feedBlogPostList[index] = current.copyWith(wasLikedByMe: wasLikedByMe, totalLikes: wasLikedByMe ? current.totalLikes + 1 : current.totalLikes - 1);
|
||||
if (!isClosed) {
|
||||
emit(FeedState.loaded(feedBlogPostList: feedBlogPostList, isLoading: isLoading));
|
||||
}
|
||||
@@ -81,5 +80,4 @@ class FeedCubit extends Cubit<FeedState> {
|
||||
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
||||
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
|
||||
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ class FeedScreen extends StatelessWidget {
|
||||
providers: [
|
||||
BlocProvider<RecipesCubit>(create: (context) => sl<RecipesCubit>()..loadRecipesList()),
|
||||
BlocProvider<FeedProfileCubit>(create: (context) => sl<FeedProfileCubit>()..getProfile()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
BlocProvider<DietsCubit>(create: (context) => sl<DietsCubit>()..loadDiets()),
|
||||
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
|
||||
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
|
||||
@@ -78,8 +77,6 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
blogPostsScrollController.dispose();
|
||||
@@ -90,8 +87,6 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
FlutterNativeSplash.remove();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
@@ -21,10 +22,7 @@ import 'package:share_plus/share_plus.dart';
|
||||
class BlogPostWidget extends StatefulWidget {
|
||||
final int index;
|
||||
|
||||
const BlogPostWidget({
|
||||
super.key,
|
||||
required this.index,
|
||||
});
|
||||
const BlogPostWidget({super.key, required this.index});
|
||||
|
||||
@override
|
||||
State<BlogPostWidget> createState() => _BlogPostWidgetState();
|
||||
@@ -44,10 +42,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
totalLikes = state.feedBlogPostList[widget.index].totalLikes;
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: state.feedBlogPostList[widget.index].id,
|
||||
),
|
||||
onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.feedBlogPostList[widget.index].id),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
@@ -70,7 +65,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
authorName: state.feedBlogPostList[widget.index].author.authorName,
|
||||
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
|
||||
? state.feedBlogPostList[widget.index].author.authorProfileIcon
|
||||
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||
: sl<AppConfig>().baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
|
||||
),
|
||||
);
|
||||
@@ -81,7 +76,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
foregroundImage: NetworkImage(
|
||||
state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
|
||||
? state.feedBlogPostList[widget.index].author.authorProfileIcon
|
||||
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||
: sl<AppConfig>().baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||
),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
),
|
||||
@@ -95,21 +90,12 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
Text(
|
||||
state.feedBlogPostList[widget.index].author.authorName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
),
|
||||
style: const TextStyle(height: 1, color: Colors.black87, fontWeight: FontWeight.w500, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black45,
|
||||
fontSize: 14,
|
||||
),
|
||||
style: const TextStyle(height: 1, color: Colors.black45, fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -119,9 +105,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: SubscribeButtonWidget(
|
||||
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||
),
|
||||
child: SubscribeButtonWidget(slug: state.feedBlogPostList[widget.index].author.authorSlug),
|
||||
),
|
||||
// Кнопка меню
|
||||
CustomPopup(
|
||||
@@ -141,11 +125,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
state.feedBlogPostList[widget.index].title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -156,15 +136,12 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Html(
|
||||
data: (state.feedBlogPostList[widget.index].description.length > 200
|
||||
? state.feedBlogPostList[widget.index].description.substring(0, 200)
|
||||
: state.feedBlogPostList[widget.index].description).replaceAll('\n', '<br>'),
|
||||
style: {
|
||||
"*": Style(
|
||||
fontSize: FontSize(16),
|
||||
color: Colors.black87,
|
||||
),
|
||||
},
|
||||
data:
|
||||
(state.feedBlogPostList[widget.index].description.length > 200
|
||||
? state.feedBlogPostList[widget.index].description.substring(0, 200)
|
||||
: state.feedBlogPostList[widget.index].description)
|
||||
.replaceAll('\n', '<br>'),
|
||||
style: {"*": Style(fontSize: FontSize(16), color: Colors.black87)},
|
||||
),
|
||||
),
|
||||
if (state.feedBlogPostList[widget.index].description.length > 200)
|
||||
@@ -172,12 +149,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
'Показать ещё',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF8B8B8B),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty)
|
||||
@@ -186,18 +158,15 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
rootNavigator: true,
|
||||
PhotoViewScreen(
|
||||
imageUrls: ['$baseUrl/storage/${state.feedBlogPostList[widget.index].image}'],
|
||||
index: 0,
|
||||
),
|
||||
PhotoViewScreen(imageUrls: ['${sl<AppConfig>().baseUrl}/storage/${state.feedBlogPostList[widget.index].image}'], index: 0),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: '$baseUrl/storage/${state.feedBlogPostList[widget.index].image}',
|
||||
tag: '${sl<AppConfig>().baseUrl}/storage/${state.feedBlogPostList[widget.index].image}',
|
||||
child: CachedNetworkImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
imageUrl: '$baseUrl/storage/${state.feedBlogPostList[widget.index].image}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.feedBlogPostList[widget.index].image}',
|
||||
errorWidget: (context, str, object) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
@@ -218,11 +187,14 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
isLiked = !isLiked;
|
||||
isLiked ? totalLikes++ : totalLikes--;
|
||||
});
|
||||
isLiked
|
||||
? await context.read<FeedCubit>().likePost(state.feedBlogPostList[widget.index].id)
|
||||
: await context.read<FeedCubit>().unlikedPost(state.feedBlogPostList[widget.index].id);
|
||||
if (!context.mounted) return;
|
||||
await context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
// Update feed list immediately by ID (no reload)
|
||||
context.read<FeedCubit>().updateFeedBlogPostList(state.feedBlogPostList[widget.index], isLiked);
|
||||
// Fire network call without introducing UI delay
|
||||
if (isLiked) {
|
||||
await context.read<FeedCubit>().likePost(state.feedBlogPostList[widget.index].id);
|
||||
} else {
|
||||
await context.read<FeedCubit>().unlikedPost(state.feedBlogPostList[widget.index].id);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -231,16 +203,10 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
'assets/icons/comment.svg',
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.gray600,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
|
||||
),
|
||||
text: state.feedBlogPostList[widget.index].totalComments.toString(),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: state.feedBlogPostList[widget.index].id,
|
||||
),
|
||||
onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.feedBlogPostList[widget.index].id),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FeedIconButton(
|
||||
@@ -254,28 +220,18 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse(
|
||||
'$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}',
|
||||
),
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/blog_post/${state.feedBlogPostList[widget.index].id}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
Icons.visibility_rounded,
|
||||
size: 16,
|
||||
color: appTheme.gray500,
|
||||
),
|
||||
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
state.feedBlogPostList[widget.index].totalViews.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.gray500,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/comments/presentation/comments_actions_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
@@ -99,13 +99,13 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
||||
extra: Author(
|
||||
authorSlug: comment.authorSlug,
|
||||
authorName: comment.authorName,
|
||||
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
|
||||
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : sl<AppConfig>().baseUrl + comment.authorProfileIcon,
|
||||
authorIsMe: isMe,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: CircleAvatar(
|
||||
foregroundImage: NetworkImage(comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon),
|
||||
foregroundImage: NetworkImage(comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : sl<AppConfig>().baseUrl + comment.authorProfileIcon),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
radius: radius,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
@@ -14,10 +14,7 @@ import 'package:go_router/go_router.dart';
|
||||
class ConsultantWidget extends StatelessWidget {
|
||||
final FeedConsultantModel consultant;
|
||||
|
||||
const ConsultantWidget({
|
||||
super.key,
|
||||
required this.consultant,
|
||||
});
|
||||
const ConsultantWidget({super.key, required this.consultant});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -36,10 +33,7 @@ class ConsultantWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color(0xFFF6F6F6),
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -51,46 +45,32 @@ class ConsultantWidget extends StatelessWidget {
|
||||
children: [
|
||||
Ink(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(
|
||||
'${consultant.ratingPlace}',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: appTheme.blueA400,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: TextStyle(fontSize: 20, color: appTheme.blueA400, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(baseUrl + consultant.photo),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
),
|
||||
if (consultant.photo.contains('https'))
|
||||
CircleAvatar(foregroundImage: NetworkImage(consultant.photo), backgroundImage: const AssetImage('assets/images/no_avatar.png'))
|
||||
else
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + consultant.photo),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
consultant.name,
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
style: const TextStyle(height: 1, fontWeight: FontWeight.w500, fontSize: 15, color: Color(0xFF323F4B)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Рейтинг: ${consultant.totalScores}',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
),
|
||||
style: TextStyle(color: appTheme.blueA400, height: 1, fontWeight: FontWeight.w500, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -98,21 +78,13 @@ class ConsultantWidget extends StatelessWidget {
|
||||
),
|
||||
Ink(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/icons/go_to_arrow.svg',
|
||||
),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), color: Colors.white),
|
||||
child: SvgPicture.asset('assets/icons/go_to_arrow.svg'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
consultant.description,
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(consultant.description, maxLines: 2),
|
||||
if (consultant.badges.isNotEmpty) const SizedBox(height: 8),
|
||||
_badges(consultant.badges, context),
|
||||
],
|
||||
@@ -128,16 +100,8 @@ class ConsultantWidget extends StatelessWidget {
|
||||
children: [
|
||||
for (int i = 0; i < badges.length; i++) ...[
|
||||
badges[i].image.contains('svg')
|
||||
? SvgPicture.network(
|
||||
'$baseUrl/storage/${badges[i].image}',
|
||||
width: 28,
|
||||
height: 28,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${badges[i].image}',
|
||||
width: 28,
|
||||
height: 28,
|
||||
),
|
||||
? SvgPicture.network('${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28)
|
||||
: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28),
|
||||
i < badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
@@ -56,7 +57,7 @@ class _DietsWidgetState extends State<DietsWidget> {
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: '$baseUrl/storage/${widget.diet.image}',
|
||||
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.image}',
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
@@ -189,7 +190,7 @@ class _DietsWidgetState extends State<DietsWidget> {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
final result = await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('$baseUrl/ru/diet/${widget.diet.id}'),
|
||||
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/${widget.diet.id}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user