From 18753b214dfeed56cde17fe8b3c69d1eb60793f7 Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Thu, 16 Oct 2025 19:46:29 +0900 Subject: [PATCH] =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D0=BE,=20?= =?UTF-8?q?=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=BD=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?,=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0=20"=D1=8D=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=81=D0=B8?= =?UTF-8?q?=D0=B3=D0=BD=D0=B0=D0=BB=D1=8B"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 + .../smartsoltech/wellshe/data/AppDatabase.kt | 9 +- .../kr/smartsoltech/wellshe/data/dao/Daos.kt | 21 - .../wellshe/data/entity/CycleHistoryEntity.kt | 1 - .../wellshe/data/entity/CyclePeriodEntity.kt | 1 - .../wellshe/data/entity/Entities.kt | 19 +- .../wellshe/data/entity/HealthRecordEntity.kt | 3 - .../wellshe/data/network/ApiClient.kt | 2 +- .../data/repository/CycleRepository.kt | 4 - .../data/repository/WellSheRepository.kt | 241 +---- .../kr/smartsoltech/wellshe/di/AppModule.kt | 118 ++- .../smartsoltech/wellshe/di/NetworkModule.kt | 26 +- .../domain/analytics/SleepAnalytics.kt | 14 - .../wellshe/domain/model/AppSettings.kt | 17 +- .../smartsoltech/wellshe/domain/model/User.kt | 1 - .../wellshe/ui/dashboard/DashboardScreen.kt | 108 +-- .../ui/dashboard/DashboardViewModel.kt | 191 +--- .../wellshe/ui/health/HealthOverviewScreen.kt | 102 +- .../wellshe/ui/health/HealthScreen.kt | 8 - .../wellshe/ui/health/HealthViewModel.kt | 158 ++-- .../wellshe/ui/mood/MoodScreen.kt | 258 ------ .../wellshe/ui/mood/MoodViewModel.kt | 33 - .../wellshe/ui/navigation/AppNavGraph.kt | 20 +- .../wellshe/ui/navigation/BottomNavItem.kt | 14 +- .../wellshe/ui/navigation/BottomNavigation.kt | 2 +- .../wellshe/ui/settings/SettingsScreen.kt | 43 +- .../wellshe/ui/settings/SettingsViewModel.kt | 92 +- .../wellshe/ui/sleep/SleepScreen.kt | 875 ------------------ .../wellshe/ui/sleep/SleepTrackingScreen.kt | 675 -------------- .../wellshe/ui/sleep/SleepViewModel.kt | 335 ------- .../main/res/xml/network_security_config.xml | 3 +- .../domain/analytics/CycleAnalyticsTest.kt | 15 +- .../domain/analytics/SleepAnalyticsTest.kt | 33 - gradle.properties | 4 +- gradle/libs.versions.toml | 2 + 35 files changed, 305 insertions(+), 3147 deletions(-) delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalytics.kt delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodScreen.kt delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodViewModel.kt delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepScreen.kt delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepTrackingScreen.kt delete mode 100644 app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepViewModel.kt delete mode 100644 app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalyticsTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fe85dd1..3666ff9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,6 +27,8 @@ android { ) } } + + buildConfigField("String", "API_BASE_URL", "\"${project.findProperty("API_BASE_URL")}\"") } buildTypes { @@ -48,6 +50,7 @@ android { buildFeatures { compose = true viewBinding = true + buildConfig = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.14" @@ -65,6 +68,7 @@ dependencies { implementation(libs.androidx.compose.material3) implementation("androidx.hilt:hilt-navigation-compose:1.1.0") implementation(libs.hilt.android) + implementation(libs.material) kapt(libs.hilt.compiler) implementation("androidx.room:room-runtime:2.6.1") kapt("androidx.room:room-compiler:2.6.1") diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/AppDatabase.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/AppDatabase.kt index 454d03a..c145cfb 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/AppDatabase.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/AppDatabase.kt @@ -12,7 +12,6 @@ import androidx.room.TypeConverter entities = [ // Основные сущности WaterLogEntity::class, - SleepLogEntity::class, WorkoutEntity::class, CalorieEntity::class, StepsEntity::class, @@ -43,13 +42,12 @@ import androidx.room.TypeConverter ExerciseFormulaVar::class, CatalogVersion::class ], - version = 11, + version = 13, // Увеличиваем версию базы данных после удаления полей mood и stressLevel exportSchema = true ) @TypeConverters(LocalDateConverter::class, InstantConverter::class, StringListConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun waterLogDao(): WaterLogDao - abstract fun sleepLogDao(): SleepLogDao abstract fun workoutDao(): WorkoutDao abstract fun calorieDao(): CalorieDao abstract fun stepsDao(): StepsDao @@ -63,6 +61,8 @@ abstract class AppDatabase : RoomDatabase() { abstract fun cycleForecastDao(): CycleForecastDao // Дополнительные DAO для repo + abstract fun beverageDao(): BeverageDao + abstract fun beverageServingDao(): BeverageServingDao abstract fun beverageLogDao(): BeverageLogDao abstract fun beverageLogNutrientDao(): BeverageLogNutrientDao abstract fun beverageServingNutrientDao(): BeverageServingNutrientDao @@ -71,8 +71,11 @@ abstract class AppDatabase : RoomDatabase() { abstract fun workoutSessionParamDao(): WorkoutSessionParamDao abstract fun workoutEventDao(): WorkoutEventDao abstract fun exerciseDao(): ExerciseDao + abstract fun exerciseParamDao(): ExerciseParamDao abstract fun exerciseFormulaDao(): ExerciseFormulaDao abstract fun exerciseFormulaVarDao(): ExerciseFormulaVarDao + abstract fun nutrientDao(): NutrientDao + abstract fun catalogVersionDao(): CatalogVersionDao } class LocalDateConverter { diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/dao/Daos.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/dao/Daos.kt index 2400fb5..87a1012 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/dao/Daos.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/dao/Daos.kt @@ -5,27 +5,6 @@ import kotlinx.coroutines.flow.Flow import kr.smartsoltech.wellshe.data.entity.* import java.time.LocalDate -@Dao -interface SleepLogDao { - @Query("SELECT * FROM sleep_logs WHERE date = :date") - suspend fun getSleepForDate(date: LocalDate): SleepLogEntity? - - @Query("SELECT * FROM sleep_logs ORDER BY date DESC LIMIT 7") - fun getRecentSleepLogs(): Flow> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertSleepLog(sleepLog: SleepLogEntity) - - @Update - suspend fun updateSleepLog(sleepLog: SleepLogEntity) - - @Delete - suspend fun deleteSleepLog(sleepLog: SleepLogEntity) - - @Query("SELECT * FROM sleep_logs WHERE date BETWEEN :startDate AND :endDate ORDER BY date DESC") - fun getSleepLogsForPeriod(startDate: LocalDate, endDate: LocalDate): Flow> -} - @Dao interface WorkoutDao { @Query("SELECT * FROM workouts WHERE date = :date ORDER BY id DESC") diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CycleHistoryEntity.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CycleHistoryEntity.kt index 5274c6a..116c9fd 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CycleHistoryEntity.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CycleHistoryEntity.kt @@ -22,6 +22,5 @@ data class CycleHistoryEntity( // Добавляем поля для соответствия с CyclePeriodEntity val flow: String = "", val symptoms: List = emptyList(), - val mood: String = "", val cycleLength: Int? = null ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CyclePeriodEntity.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CyclePeriodEntity.kt index d60579c..c8f1409 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CyclePeriodEntity.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/CyclePeriodEntity.kt @@ -11,6 +11,5 @@ data class CyclePeriodEntity( val endDate: LocalDate?, val flow: String = "", val symptoms: List = emptyList(), - val mood: String = "", val cycleLength: Int? = null ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/Entities.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/Entities.kt index 35e0441..ff54bb5 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/Entities.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/Entities.kt @@ -13,18 +13,6 @@ data class WaterLogEntity( val timestamp: Long = System.currentTimeMillis() ) -@Entity(tableName = "sleep_logs") -data class SleepLogEntity( - @PrimaryKey(autoGenerate = true) - val id: Long = 0, - val date: LocalDate, - val bedTime: String, // HH:mm - val wakeTime: String, // HH:mm - val duration: Float, // часы - val quality: String = "good", // poor, fair, good, excellent - val notes: String = "" -) - @Entity(tableName = "workouts") data class WorkoutEntity( @PrimaryKey(autoGenerate = true) @@ -76,5 +64,10 @@ data class UserProfileEntity( val cycleLength: Int = 28, val periodLength: Int = 5, val lastPeriodDate: LocalDate? = null, - val profileImagePath: String = "" + val profileImagePath: String = "", + val emergency_contact_1_name: String? = null, + val emergency_contact_1_phone: String? = null, + val emergency_contact_2_name: String? = null, + val emergency_contact_2_phone: String? = null, + val emergency_notifications_enabled: Boolean? = false ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/HealthRecordEntity.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/HealthRecordEntity.kt index 26766e9..8b95b7a 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/entity/HealthRecordEntity.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/entity/HealthRecordEntity.kt @@ -13,10 +13,7 @@ data class HealthRecordEntity( val bloodPressureS: Int?, val bloodPressureD: Int?, val temperature: Float?, - val mood: String?, val energyLevel: Int?, - val stressLevel: Int?, val symptoms: List?, val notes: String? ) - diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/network/ApiClient.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/network/ApiClient.kt index a828886..1826de1 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/network/ApiClient.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/network/ApiClient.kt @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit * Класс для настройки и создания API-клиентов */ object ApiClient { - private const val BASE_URL = "http://192.168.0.112:8000/api/v1/" + private const val BASE_URL = "http://192.168.219.108:8000/api/v1/" private const val CONNECT_TIMEOUT = 15L private const val READ_TIMEOUT = 15L private const val WRITE_TIMEOUT = 15L diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/repository/CycleRepository.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/repository/CycleRepository.kt index f54bc2e..d858ac8 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/repository/CycleRepository.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/repository/CycleRepository.kt @@ -263,7 +263,6 @@ class CycleRepository @Inject constructor( endDate = historyEntity.periodEnd, flow = historyEntity.flow, symptoms = historyEntity.symptoms, - mood = historyEntity.mood, cycleLength = historyEntity.cycleLength ) } @@ -277,7 +276,6 @@ class CycleRepository @Inject constructor( periodEnd = period.endDate, flow = period.flow, symptoms = period.symptoms, - mood = period.mood, cycleLength = period.cycleLength, atypical = false // по умолчанию не отмечаем как нетипичный ) @@ -292,7 +290,6 @@ class CycleRepository @Inject constructor( periodEnd = period.endDate, flow = period.flow, symptoms = period.symptoms, - mood = period.mood, cycleLength = period.cycleLength, atypical = false // сохраняем существующее значение, если возможно ) @@ -306,7 +303,6 @@ class CycleRepository @Inject constructor( periodEnd = period.endDate, flow = period.flow, symptoms = period.symptoms, - mood = period.mood, cycleLength = period.cycleLength, atypical = false ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/data/repository/WellSheRepository.kt b/app/src/main/java/kr/smartsoltech/wellshe/data/repository/WellSheRepository.kt index cc8ad1b..b7d2035 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/data/repository/WellSheRepository.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/data/repository/WellSheRepository.kt @@ -12,7 +12,6 @@ import kr.smartsoltech.wellshe.domain.model.User import kr.smartsoltech.wellshe.domain.model.WaterIntake import kr.smartsoltech.wellshe.domain.model.WorkoutSession import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime import javax.inject.Inject import javax.inject.Singleton @@ -21,7 +20,6 @@ import javax.inject.Singleton class WellSheRepository @Inject constructor( private val waterLogDao: WaterLogDao, private val cyclePeriodDao: CyclePeriodDao, - private val sleepLogDao: SleepLogDao, private val healthRecordDao: HealthRecordDao, private val workoutDao: WorkoutDao, private val calorieDao: CalorieDao, @@ -45,8 +43,7 @@ class WellSheRepository @Inject constructor( weight = 60f, dailyWaterGoal = 2.5f, dailyStepsGoal = 10000, - dailyCaloriesGoal = 2000, - dailySleepGoal = 8.0f + dailyCaloriesGoal = 2000 ) ) } @@ -157,231 +154,89 @@ class WellSheRepository @Inject constructor( // TODO: Реализовать окончание тренировки } - // ================= - // СОН - // ================= - - suspend fun getSleepForDate(date: LocalDate): SleepLogEntity? { - return sleepLogDao.getSleepForDate(date) - } - - fun getRecentSleepLogs(): Flow> { - return sleepLogDao.getRecentSleepLogs() - } - - suspend fun addSleepRecord(date: LocalDate, bedTime: String, wakeTime: String, quality: String, notes: String) { - // Вычисляем продолжительность сна - val duration = calculateSleepDuration(bedTime, wakeTime) - - sleepLogDao.insertSleepLog( - SleepLogEntity( - date = date, - bedTime = bedTime, - wakeTime = wakeTime, - duration = duration, - quality = quality, - notes = notes - ) - ) - } - - private fun calculateSleepDuration(bedTime: String, wakeTime: String): Float { - // TODO: Реализовать правильный расчет продолжительности сна - return 8.0f - } - // ================= // МЕНСТРУАЛЬНЫЙ ЦИКЛ // ================= - suspend fun addPeriod(startDate: LocalDate, endDate: LocalDate?, flow: String, symptoms: List, mood: String) { + suspend fun addPeriod(startDate: LocalDate, endDate: LocalDate?, flow: String, symptoms: List) { val period = CyclePeriodEntity( startDate = startDate, endDate = endDate, flow = flow, - symptoms = symptoms, - mood = mood + symptoms = symptoms ) - cyclePeriodDao.insert(period) + // Используем CycleRepository для работы с периодами + // cyclePeriodDao.insertPeriod(period) + // TODO: Добавить интеграцию с CycleRepository } - suspend fun updatePeriod(periodId: Long, endDate: LocalDate?, flow: String, symptoms: List, mood: String) { - val periods = cyclePeriodDao.getAll() - val existingPeriod = periods.firstOrNull { it.id == periodId } - if (existingPeriod != null) { - val updatedPeriod = existingPeriod.copy( - endDate = endDate, - flow = flow, - symptoms = symptoms, - mood = mood - ) - cyclePeriodDao.update(updatedPeriod) - } + suspend fun updatePeriod(periodId: Long, endDate: LocalDate?, flow: String, symptoms: List) { + // TODO: Реализовать через CycleRepository + // val existingPeriod = cyclePeriodDao.getPeriodById(periodId) + // existingPeriod?.let { + // val updatedPeriod = it.copy( + // endDate = endDate, + // flow = flow, + // symptoms = symptoms + // ) + // cyclePeriodDao.updatePeriod(updatedPeriod) + // } } - suspend fun getRecentPeriods(): List { - return cyclePeriodDao.getAll().take(6) + fun getPeriods(): Flow> { + // TODO: Реализовать через CycleRepository + return flowOf(emptyList()) + // return cyclePeriodDao.getAllPeriods() + } + + suspend fun deletePeriod(periodId: Long) { + // TODO: Реализовать через CycleRepository + // cyclePeriodDao.deletePeriodById(periodId) } // ================= // НАСТРОЙКИ // ================= - fun getSettings(): Flow { + fun getAppSettings(): Flow { // TODO: Реализовать получение настроек из БД return flowOf( AppSettings( - isWaterReminderEnabled = true, - isCycleReminderEnabled = true, - isSleepReminderEnabled = true, - cycleLength = 28, - periodLength = 5, - waterGoal = 2.5f, - stepsGoal = 10000, - sleepGoal = 8.0f, - isDarkTheme = false + notificationsEnabled = true, + darkModeEnabled = false ) ) } - suspend fun updateWaterReminderSetting(enabled: Boolean) { - // TODO: Реализовать обновление настройки напоминаний о воде - } - - suspend fun updateCycleReminderSetting(enabled: Boolean) { - // TODO: Реализовать обновление настройки напоминаний о цикле - } - - suspend fun updateSleepReminderSetting(enabled: Boolean) { - // TODO: Реализовать обновление настройки напоминаний о сне - } - - suspend fun updateCycleLength(length: Int) { - // TODO: Реализовать обновление длины цикла - } - - suspend fun updatePeriodLength(length: Int) { - // TODO: Реализовать обновление длины менструации - } - - suspend fun updateStepsGoal(goal: Int) { - // TODO: Реализовать обновление цели по шагам - } - - suspend fun updateSleepGoal(goal: Float) { - // TODO: Реализовать обновление цели по сну - } - - suspend fun updateThemeSetting(isDark: Boolean) { - // TODO: Реализовать обновление темы + suspend fun updateAppSettings(settings: AppSettings) { + // TODO: Реализовать обновление настроек } // ================= - // УПРАВЛЕНИЕ ДАННЫМИ + // АНАЛИТИКА И ОТЧЕТЫ // ================= - suspend fun exportUserData() { - // TODO: Реализовать экспорт данных пользователя - } - - suspend fun importUserData() { - // TODO: Реализовать импорт данных пользователя - } - - suspend fun clearAllUserData() { - // TODO: Реализовать очистку всех данных пользователя - } - - // ================= - // ЗДОРОВЬЕ - // ================= - - fun getTodayHealthData(): kotlinx.coroutines.flow.Flow { - val today = LocalDate.now() - return healthRecordDao.getByDateFlow(today) - } - - fun getAllHealthRecords(): kotlinx.coroutines.flow.Flow> { - return healthRecordDao.getAllFlow() - } - - fun getRecentHealthRecords(limit: Int = 10): kotlinx.coroutines.flow.Flow> { - return healthRecordDao.getAllFlow().map { records: List -> - records.sortedByDescending { r -> r.date }.take(limit) - } - } - - suspend fun saveHealthRecord(record: HealthRecordEntity) { - if (record.id != 0L) { - healthRecordDao.update(record) - } else { - healthRecordDao.insert(record) - } - } - - suspend fun deleteHealthRecord(recordId: Long) { - val record = healthRecordDao.getAll().firstOrNull { it.id == recordId } - if (record != null) { - healthRecordDao.delete(record) - } - } - - // ================= - // DASHBOARD - // ================= - - fun getDashboardData(): Flow { - // TODO: Реализовать получение данных для главного экрана - return flowOf( - DashboardData( - user = User(), - todayHealth = null, - sleepData = null, - cycleData = null, - recentWorkouts = emptyList() - ) - ) - } - - // ================= - // УСТАРЕВШИЕ МЕТОДЫ (для совместимости) - // ================= - - suspend fun addWater(amount: Int, date: LocalDate = LocalDate.now()) { - waterLogDao.insertWaterLog( - WaterLogEntity(date = date, amount = amount) - ) - } - - suspend fun getTodayWaterIntake(date: LocalDate = LocalDate.now()): Int { - return waterLogDao.getTotalWaterForDate(date) ?: 0 - } - - fun getWaterLogsForDate(date: LocalDate): Flow> { + fun getDashboardData(date: LocalDate): Flow { return flow { - emit(waterLogDao.getWaterLogsForDate(date)) + emit( + DashboardData( + date = date, + waterIntake = 1.2f, + steps = 6500, + calories = 1850, + workouts = 1, + cycleDay = null + ) + ) } } } -// Вспомогательные data классы data class DashboardData( - val user: User, - val todayHealth: HealthRecord?, - val sleepData: SleepLogEntity?, - val cycleData: CyclePeriodEntity?, - val recentWorkouts: List -) - -data class HealthRecord( - val id: Long = 0, val date: LocalDate, - val bloodPressureSystolic: Int = 0, - val bloodPressureDiastolic: Int = 0, - val heartRate: Int = 0, - val weight: Float = 0f, - val mood: String = "neutral", // Добавляем поле настроения - val energyLevel: Int = 5, // Добавляем уровень энергии (1-10) - val stressLevel: Int = 5, // Добавляем уровень стресса (1-10) - val notes: String = "" + val waterIntake: Float, + val steps: Int, + val calories: Int, + val workouts: Int, + val cycleDay: Int? ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/di/AppModule.kt b/app/src/main/java/kr/smartsoltech/wellshe/di/AppModule.kt index 6db9c79..12ff9f4 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/di/AppModule.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/di/AppModule.kt @@ -8,21 +8,8 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kr.smartsoltech.wellshe.data.AppDatabase -import kr.smartsoltech.wellshe.data.datastore.DataStoreManager import kr.smartsoltech.wellshe.data.dao.* -import kr.smartsoltech.wellshe.data.repo.DrinkLogger -import kr.smartsoltech.wellshe.data.repo.WeightRepository -import kr.smartsoltech.wellshe.data.repo.WorkoutService -import kr.smartsoltech.wellshe.data.MIGRATION_1_2 -import kr.smartsoltech.wellshe.data.MIGRATION_2_3 -import kr.smartsoltech.wellshe.data.MIGRATION_3_4 -import kr.smartsoltech.wellshe.data.MIGRATION_4_5 -import kr.smartsoltech.wellshe.data.MIGRATION_5_6 -import kr.smartsoltech.wellshe.data.MIGRATION_6_7 -import kr.smartsoltech.wellshe.data.MIGRATION_7_8 -import kr.smartsoltech.wellshe.data.MIGRATION_8_9 -import kr.smartsoltech.wellshe.data.MIGRATION_9_10 -import kr.smartsoltech.wellshe.data.MIGRATION_10_11 +import kr.smartsoltech.wellshe.data.repo.* import javax.inject.Singleton @Module @@ -31,34 +18,18 @@ object AppModule { @Provides @Singleton - fun provideDataStoreManager(@ApplicationContext context: Context): DataStoreManager = - DataStoreManager(context) - - @Provides - @Singleton - fun provideDatabase(@ApplicationContext context: Context): AppDatabase = - Room.databaseBuilder( + fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { + return Room.databaseBuilder( context, AppDatabase::class.java, - "well_she_db" - ) - .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11) - .fallbackToDestructiveMigration() - .build() + "wellshe_database" + ).build() + } - // DAO providers + // DAO Providers @Provides fun provideWaterLogDao(database: AppDatabase): WaterLogDao = database.waterLogDao() - @Provides - fun provideCyclePeriodDao(database: AppDatabase): CyclePeriodDao = database.cyclePeriodDao() - - @Provides - fun provideSleepLogDao(database: AppDatabase): SleepLogDao = database.sleepLogDao() - - @Provides - fun provideHealthRecordDao(database: AppDatabase): HealthRecordDao = database.healthRecordDao() - @Provides fun provideWorkoutDao(database: AppDatabase): WorkoutDao = database.workoutDao() @@ -71,7 +42,12 @@ object AppModule { @Provides fun provideUserProfileDao(database: AppDatabase): UserProfileDao = database.userProfileDao() - // DAO для BodyRepo + @Provides + fun provideCyclePeriodDao(database: AppDatabase): CyclePeriodDao = database.cyclePeriodDao() + + @Provides + fun provideHealthRecordDao(database: AppDatabase): HealthRecordDao = database.healthRecordDao() + @Provides fun provideBeverageLogDao(database: AppDatabase): BeverageLogDao = database.beverageLogDao() @@ -102,7 +78,28 @@ object AppModule { @Provides fun provideExerciseFormulaVarDao(database: AppDatabase): ExerciseFormulaVarDao = database.exerciseFormulaVarDao() - // Repo providers + @Provides + fun provideBeverageDao(database: AppDatabase): BeverageDao = database.beverageDao() + + @Provides + fun provideBeverageServingDao(database: AppDatabase): BeverageServingDao = database.beverageServingDao() + + @Provides + fun provideExerciseParamDao(database: AppDatabase): ExerciseParamDao = database.exerciseParamDao() + + @Provides + fun provideNutrientDao(database: AppDatabase): NutrientDao = database.nutrientDao() + + @Provides + fun provideCatalogVersionDao(database: AppDatabase): CatalogVersionDao = database.catalogVersionDao() + + // Repository/Service Providers + @Provides + @Singleton + fun provideWeightRepository(weightLogDao: WeightLogDao): WeightRepository { + return WeightRepository(weightLogDao) + } + @Provides @Singleton fun provideDrinkLogger( @@ -110,12 +107,9 @@ object AppModule { beverageLogDao: BeverageLogDao, beverageLogNutrientDao: BeverageLogNutrientDao, servingNutrientDao: BeverageServingNutrientDao - ): DrinkLogger = DrinkLogger(waterLogDao, beverageLogDao, beverageLogNutrientDao, servingNutrientDao) - - @Provides - @Singleton - fun provideWeightRepository(weightLogDao: WeightLogDao): WeightRepository = - WeightRepository(weightLogDao) + ): DrinkLogger { + return DrinkLogger(waterLogDao, beverageLogDao, beverageLogNutrientDao, servingNutrientDao) + } @Provides @Singleton @@ -127,23 +121,27 @@ object AppModule { formulaDao: ExerciseFormulaDao, formulaVarDao: ExerciseFormulaVarDao, exerciseDao: ExerciseDao - ): WorkoutService = WorkoutService(sessionDao, paramDao, eventDao, weightRepo, formulaDao, formulaVarDao, exerciseDao) + ): WorkoutService { + return WorkoutService(sessionDao, paramDao, eventDao, weightRepo, formulaDao, formulaVarDao, exerciseDao) + } - // Repository @Provides @Singleton - fun provideWellSheRepository( - waterLogDao: WaterLogDao, - cyclePeriodDao: CyclePeriodDao, - sleepLogDao: SleepLogDao, - healthRecordDao: HealthRecordDao, - workoutDao: WorkoutDao, - calorieDao: CalorieDao, - stepsDao: StepsDao, - userProfileDao: UserProfileDao - ): kr.smartsoltech.wellshe.data.repository.WellSheRepository = - kr.smartsoltech.wellshe.data.repository.WellSheRepository( - waterLogDao, cyclePeriodDao, sleepLogDao, healthRecordDao, - workoutDao, calorieDao, stepsDao, userProfileDao - ) + fun provideBeverageCatalogRepository( + beverageDao: BeverageDao, + servingDao: BeverageServingDao, + servingNutrientDao: BeverageServingNutrientDao + ): BeverageCatalogRepository { + return BeverageCatalogRepository(beverageDao, servingDao, servingNutrientDao) + } + + @Provides + @Singleton + fun provideExerciseCatalogRepository( + exerciseDao: ExerciseDao, + paramDao: ExerciseParamDao, + formulaDao: ExerciseFormulaDao + ): ExerciseCatalogRepository { + return ExerciseCatalogRepository(exerciseDao, paramDao, formulaDao) + } } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/di/NetworkModule.kt b/app/src/main/java/kr/smartsoltech/wellshe/di/NetworkModule.kt index 40faba0..d5a06c4 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/di/NetworkModule.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/di/NetworkModule.kt @@ -6,6 +6,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kr.smartsoltech.wellshe.BuildConfig import kr.smartsoltech.wellshe.data.local.AuthTokenRepository import kr.smartsoltech.wellshe.data.network.AuthInterceptor import okhttp3.OkHttpClient @@ -18,8 +19,6 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object NetworkModule { - - private const val BASE_URL = "http://192.168.0.112:8000/api/v1/" private const val CONNECT_TIMEOUT = 15L private const val READ_TIMEOUT = 15L private const val WRITE_TIMEOUT = 15L @@ -40,27 +39,20 @@ object NetworkModule { @Provides @Singleton - fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient { - val loggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - } - - return OkHttpClient.Builder() + fun provideRetrofit(gson: Gson, authInterceptor: AuthInterceptor): Retrofit { + val client = OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }) .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) - .addInterceptor(loggingInterceptor) - .addInterceptor(authInterceptor) .build() - } - - @Provides - @Singleton - fun provideRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit { return Retrofit.Builder() - .baseUrl(BASE_URL) - .client(okHttpClient) + .baseUrl(BuildConfig.API_BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) + .client(client) .build() } } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalytics.kt b/app/src/main/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalytics.kt deleted file mode 100644 index 950252c..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalytics.kt +++ /dev/null @@ -1,14 +0,0 @@ -package kr.smartsoltech.wellshe.domain.analytics - -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity - -object SleepAnalytics { - /** - * Расчёт долга сна и недельного тренда - */ - fun sleepDebt(logs: List, targetHours: Int = 8): Int { - val total = logs.sumOf { it.duration.toDouble() } - val expected = logs.size * targetHours - return (expected - total).toInt() - } -} diff --git a/app/src/main/java/kr/smartsoltech/wellshe/domain/model/AppSettings.kt b/app/src/main/java/kr/smartsoltech/wellshe/domain/model/AppSettings.kt index 774fdb0..d2ef9b3 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/domain/model/AppSettings.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/domain/model/AppSettings.kt @@ -1,19 +1,6 @@ package kr.smartsoltech.wellshe.domain.model data class AppSettings( - val id: Long = 0, - val isWaterReminderEnabled: Boolean = true, - val waterReminderInterval: Int = 2, // часы - val isCycleReminderEnabled: Boolean = true, - val isSleepReminderEnabled: Boolean = true, - val sleepReminderTime: String = "22:00", - val wakeUpReminderTime: String = "07:00", - val cycleLength: Int = 28, - val periodLength: Int = 5, - val waterGoal: Float = 2.5f, - val stepsGoal: Int = 10000, - val sleepGoal: Float = 8.0f, - val isDarkTheme: Boolean = false, - val language: String = "ru", - val isFirstLaunch: Boolean = true + val notificationsEnabled: Boolean = true, + val darkModeEnabled: Boolean = false ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/domain/model/User.kt b/app/src/main/java/kr/smartsoltech/wellshe/domain/model/User.kt index 2a39361..2307c93 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/domain/model/User.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/domain/model/User.kt @@ -13,7 +13,6 @@ data class User( val dailyWaterGoal: Float = 2.5f, // в литрах val dailyStepsGoal: Int = 10000, val dailyCaloriesGoal: Int = 2000, - val dailySleepGoal: Float = 8.0f, // в часах val cycleLength: Int = 28, // дней val periodLength: Int = 5, // дней val lastPeriodStart: LocalDate? = null, diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardScreen.kt index 09b840e..bfaa6e3 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardScreen.kt @@ -83,13 +83,6 @@ fun DashboardScreen( ) } - item { - SleepCard( - sleepData = uiState.sleepData, - onClick = { onNavigate("sleep") } - ) - } - item { RecentWorkoutsCard( workouts = uiState.recentWorkouts, @@ -404,26 +397,26 @@ private fun HealthOverviewCard( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { - HealthMetric( - label = "Пульс", - value = "${healthData.heartRate}", - unit = "bpm", - icon = Icons.Default.Favorite - ) - - HealthMetric( - label = "Настроение", - value = getMoodEmoji(healthData.mood), - unit = "", - icon = Icons.Default.Mood - ) - HealthMetric( label = "Энергия", value = "${healthData.energyLevel}", unit = "/10", icon = Icons.Default.Battery6Bar ) + + HealthMetric( + label = "Симптомы", + value = "${healthData.symptoms.size}", + unit = "", + icon = Icons.Default.HealthAndSafety + ) + + HealthMetric( + label = "Заметки", + value = if (healthData.notes.isNotEmpty()) "✓" else "—", + unit = "", + icon = Icons.Default.Notes + ) } } } @@ -479,63 +472,6 @@ private fun HealthMetric( } } -@Composable -private fun SleepCard( - sleepData: SleepData, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier - .fillMaxWidth() - .clickable { onClick() }, - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(12.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = PrimaryPink, - modifier = Modifier.size(40.dp) - ) - - Spacer(modifier = Modifier.width(16.dp)) - - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = "Сон", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.SemiBold, - color = TextPrimary - ) - ) - - Text( - text = "${sleepData.sleepDuration}ч • ${getSleepQualityText(sleepData.sleepQuality)}", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - } - - Icon( - imageVector = Icons.Default.ChevronRight, - contentDescription = null, - tint = NeutralGray - ) - } - } -} - @Composable private fun RecentWorkoutsCard( workouts: List, @@ -605,7 +541,7 @@ private fun WorkoutItem( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = getWorkoutIcon(workout.type), + imageVector = Icons.Default.FitnessCenter, contentDescription = null, tint = PrimaryPink, modifier = Modifier.size(20.dp) @@ -617,7 +553,7 @@ private fun WorkoutItem( modifier = Modifier.weight(1f) ) { Text( - text = getWorkoutTypeText(workout.type), + text = workout.name, style = MaterialTheme.typography.bodyMedium.copy( fontWeight = FontWeight.Medium, color = TextPrimary @@ -662,12 +598,12 @@ private val quickActions = listOf( textColor = SecondaryBlue ), QuickAction( - title = "Отметить сон", - icon = Icons.Default.Bedtime, - route = "sleep", - backgroundColor = AccentPurpleLight, - iconColor = AccentPurple, - textColor = AccentPurple + title = "Экстренная помощь", + icon = Icons.Default.Emergency, + route = "emergency", + backgroundColor = ErrorRedLight, + iconColor = ErrorRed, + textColor = ErrorRed ) ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardViewModel.kt index 78974e0..b6836e5 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/dashboard/DashboardViewModel.kt @@ -8,19 +8,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch -import kr.smartsoltech.wellshe.data.entity.CyclePeriodEntity -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity -import kr.smartsoltech.wellshe.data.entity.HealthRecordEntity import kr.smartsoltech.wellshe.data.repository.WellSheRepository import kr.smartsoltech.wellshe.domain.model.* import javax.inject.Inject import java.time.LocalDate -import java.time.temporal.ChronoUnit data class DashboardUiState( val user: User = User(), val todayHealth: HealthData = HealthData(), - val sleepData: SleepData = SleepData(), val cycleData: CycleData = CycleData(), val recentWorkouts: List = emptyList(), val todaySteps: Int = 0, @@ -53,37 +48,13 @@ class DashboardViewModel @Inject constructor( _uiState.value = _uiState.value.copy(user = user) } - // Загружаем данные о здоровье - repository.getTodayHealthData().catch { - // Игнорируем ошибки, используем дефолтные данные - }.collect { healthEntity: HealthRecordEntity? -> - val healthData = healthEntity?.let { convertHealthEntityToModel(it) } ?: HealthData() - _uiState.value = _uiState.value.copy(todayHealth = healthData) - } + // TODO: Временно используем заглушки для данных о здоровье + val healthData = HealthData() + _uiState.value = _uiState.value.copy(todayHealth = healthData) - // Загружаем данные о сне - loadSleepData() - - // Загружаем данные о цикле - repository.getRecentPeriods().let { periods -> - val cycleEntity = periods.firstOrNull() - val cycleData = cycleEntity?.let { convertCycleEntityToModel(it) } ?: CycleData() - _uiState.value = _uiState.value.copy(cycleData = cycleData) - } - - // Загружаем тренировки - repository.getRecentWorkouts().catch { - // Игнорируем ошибки - }.collect { workoutEntities: List -> - val workouts = workoutEntities.map { convertWorkoutEntityToModel(it) } - _uiState.value = _uiState.value.copy(recentWorkouts = workouts) - } - - // Загружаем шаги за сегодня - loadTodayFitnessData() - - // Загружаем воду за сегодня - loadTodayWaterData() + // TODO: Временно используем заглушки для данных о цикле + val cycleData = CycleData() + _uiState.value = _uiState.value.copy(cycleData = cycleData) _uiState.value = _uiState.value.copy(isLoading = false) @@ -96,136 +67,28 @@ class DashboardViewModel @Inject constructor( } } - private suspend fun loadSleepData() { - try { - val yesterday = LocalDate.now().minusDays(1) - val sleepEntity = repository.getSleepForDate(yesterday) - val sleepData = sleepEntity?.let { convertSleepEntityToModel(it) } ?: SleepData() - _uiState.value = _uiState.value.copy(sleepData = sleepData) - } catch (_: Exception) { - // Игнорируем ошибки загрузки сна - } - } - - private suspend fun loadTodayFitnessData() { - try { - val today = LocalDate.now() - repository.getFitnessDataForDate(today).catch { - // Игнорируем ошибки - }.collect { fitnessData: FitnessData -> - _uiState.value = _uiState.value.copy(todaySteps = fitnessData.steps) - } - } catch (_: Exception) { - // Игнорируем ошибки загрузки фитнеса - } - } - - private suspend fun loadTodayWaterData() { - try { - val today = LocalDate.now() - repository.getWaterIntakeForDate(today).catch { - // Игнорируем ошибки - }.collect { waterIntakes: List -> - val totalAmount = waterIntakes.sumOf { it.amount.toDouble() }.toFloat() - _uiState.value = _uiState.value.copy(todayWater = totalAmount) - } - } catch (_: Exception) { - // Игнорируем ошибки загрузки воды - } - } - fun clearError() { _uiState.value = _uiState.value.copy(error = null) } - - // Функции преобразования Entity -> Model - private fun convertHealthEntityToModel(entity: HealthRecordEntity): HealthData { - return HealthData( - id = entity.id.toString(), - userId = "current_user", - date = entity.date, - weight = entity.weight ?: 0f, - heartRate = entity.heartRate ?: 70, - bloodPressureSystolic = entity.bloodPressureS ?: 120, - bloodPressureDiastolic = entity.bloodPressureD ?: 80, - mood = convertMoodStringToEnum(entity.mood ?: "neutral"), - energyLevel = entity.energyLevel ?: 5, - stressLevel = entity.stressLevel ?: 5, - symptoms = entity.symptoms ?: emptyList() - ) - } - - private fun convertSleepEntityToModel(entity: SleepLogEntity): SleepData { - return SleepData( - id = entity.id.toString(), - userId = "current_user", - date = entity.date, - bedTime = java.time.LocalTime.parse(entity.bedTime), - wakeTime = java.time.LocalTime.parse(entity.wakeTime), - sleepDuration = entity.duration, - sleepQuality = convertSleepQualityStringToEnum(entity.quality) - ) - } - - private fun convertCycleEntityToModel(entity: CyclePeriodEntity): CycleData { - return CycleData( - id = entity.id.toString(), - userId = "current_user", - cycleLength = entity.cycleLength ?: 28, - periodLength = entity.endDate?.let { - ChronoUnit.DAYS.between(entity.startDate, it).toInt() + 1 - } ?: 5, - lastPeriodDate = entity.startDate, - nextPeriodDate = entity.startDate.plusDays((entity.cycleLength ?: 28).toLong()), - ovulationDate = entity.startDate.plusDays(((entity.cycleLength ?: 28) / 2).toLong()) - ) - } - - private fun convertWorkoutEntityToModel(entity: kr.smartsoltech.wellshe.domain.model.WorkoutSession): WorkoutData { - return WorkoutData( - id = entity.id.toString(), - userId = "current_user", - date = entity.date, - type = convertWorkoutTypeStringToEnum(entity.type), - duration = entity.duration, - intensity = WorkoutIntensity.MODERATE, // По умолчанию, так как в WorkoutSession нет intensity - caloriesBurned = entity.caloriesBurned - ) - } - - // Вспомогательные функции преобразования - private fun convertMoodStringToEnum(mood: String): Mood { - return when (mood.lowercase()) { - "very_sad" -> Mood.VERY_SAD - "sad" -> Mood.SAD - "neutral" -> Mood.NEUTRAL - "happy" -> Mood.HAPPY - "very_happy" -> Mood.VERY_HAPPY - else -> Mood.NEUTRAL - } - } - - private fun convertSleepQualityStringToEnum(quality: String): SleepQuality { - return when (quality.lowercase()) { - "poor" -> SleepQuality.POOR - "fair" -> SleepQuality.FAIR - "good" -> SleepQuality.GOOD - "excellent" -> SleepQuality.EXCELLENT - else -> SleepQuality.GOOD - } - } - - private fun convertWorkoutTypeStringToEnum(type: String): WorkoutType { - return when (type.lowercase()) { - "кардио", "cardio" -> WorkoutType.CARDIO - "силовая", "strength" -> WorkoutType.STRENGTH - "йога", "yoga" -> WorkoutType.YOGA - "пилатес", "pilates" -> WorkoutType.PILATES - "бег", "running" -> WorkoutType.RUNNING - "ходьба", "walking" -> WorkoutType.WALKING - "велосипед", "cycling" -> WorkoutType.CYCLING - "плавание", "swimming" -> WorkoutType.SWIMMING - else -> WorkoutType.CARDIO - } - } } + +// Упрощенные модели данных для Dashboard +data class HealthData( + val energyLevel: Int = 5, + val symptoms: List = emptyList(), + val notes: String = "" +) + +data class CycleData( + val currentDay: Int = 1, + val nextPeriodDate: LocalDate? = null, + val cycleLength: Int = 28 +) + +data class WorkoutData( + val id: Long = 0, + val name: String = "", + val duration: Int = 0, + val caloriesBurned: Int = 0, + val date: LocalDate = LocalDate.now() +) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthOverviewScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthOverviewScreen.kt index 761c5c0..b0f4c9b 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthOverviewScreen.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthOverviewScreen.kt @@ -93,9 +93,7 @@ fun HealthOverviewScreen( TodayHealthCard( uiState = uiState, onUpdateVitals = viewModel::updateVitals, - onUpdateMood = viewModel::updateMood, - onUpdateEnergy = viewModel::updateEnergyLevel, - onUpdateStress = viewModel::updateStressLevel + onUpdateEnergy = viewModel::updateEnergyLevel ) } @@ -133,9 +131,7 @@ fun HealthOverviewScreen( private fun TodayHealthCard( uiState: HealthUiState, onUpdateVitals: (Float?, Int?, Int?, Int?, Float?) -> Unit, - onUpdateMood: (String) -> Unit, onUpdateEnergy: (Int) -> Unit, - onUpdateStress: (Int) -> Unit, modifier: Modifier = Modifier ) { var weight by remember { mutableStateOf(uiState.todayRecord?.weight?.toString() ?: "") } @@ -269,16 +265,7 @@ private fun TodayHealthCard( Spacer(modifier = Modifier.height(16.dp)) - // Настроение - MoodSection( - currentMood = uiState.todayRecord?.mood ?: "neutral", - onMoodChange = onUpdateMood, - isEditMode = uiState.isEditMode - ) - - Spacer(modifier = Modifier.height(16.dp)) - - // Уровень энергии и стресса + // Уровень энергии Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp) @@ -288,18 +275,9 @@ private fun TodayHealthCard( value = uiState.todayRecord?.energyLevel ?: 5, onValueChange = onUpdateEnergy, isEditMode = uiState.isEditMode, - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxWidth(), color = WarningOrange ) - - LevelSlider( - label = "Стресс", - value = uiState.todayRecord?.stressLevel ?: 5, - onValueChange = onUpdateStress, - isEditMode = uiState.isEditMode, - modifier = Modifier.weight(1f), - color = ErrorRed - ) } } } @@ -352,68 +330,6 @@ private fun VitalMetric( } } -@Composable -private fun MoodSection( - currentMood: String, - onMoodChange: (String) -> Unit, - isEditMode: Boolean, - modifier: Modifier = Modifier -) { - Column(modifier = modifier) { - Text( - text = "Настроение", - style = MaterialTheme.typography.bodyMedium.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - if (isEditMode) { - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(healthMoods) { mood -> - FilterChip( - onClick = { onMoodChange(mood.key) }, - label = { - Row(verticalAlignment = Alignment.CenterVertically) { - Text(mood.emoji) - Spacer(modifier = Modifier.width(4.dp)) - Text(mood.name) - } - }, - selected = currentMood == mood.key, - colors = FilterChipDefaults.filterChipColors( - selectedContainerColor = SuccessGreenLight, - selectedLabelColor = SuccessGreen - ) - ) - } - } - } else { - val currentMoodData = healthMoods.find { it.key == currentMood } ?: healthMoods[2] - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = currentMoodData.emoji, - style = MaterialTheme.typography.headlineSmall - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = currentMoodData.name, - style = MaterialTheme.typography.bodyLarge.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - } - } - } -} - @Composable private fun LevelSlider( label: String, @@ -679,18 +595,8 @@ private fun NotesCard( } // Данные для UI -private data class HealthMoodData(val key: String, val name: String, val emoji: String) - -private val healthMoods = listOf( - HealthMoodData("very_sad", "Очень плохо", "😢"), - HealthMoodData("sad", "Плохо", "😔"), - HealthMoodData("neutral", "Нормально", "😐"), - HealthMoodData("happy", "Хорошо", "😊"), - HealthMoodData("very_happy", "Отлично", "😄") -) - private val healthSymptoms = listOf( "Головная боль", "Усталость", "Тошнота", "Головокружение", - "Боль в спине", "Боль в суставах", "Бессонница", "Стресс", + "Боль в спине", "Боль в суставах", "Бессонница", "Простуда", "Аллергия", "Боль в животе", "Другое" ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthScreen.kt index 603677d..b4dc2f6 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthScreen.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthScreen.kt @@ -248,9 +248,7 @@ private fun VitalSignsCard( bloodPressureS = 0, bloodPressureD = 0, temperature = 36.6f, - mood = "", energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = "" ) @@ -275,9 +273,7 @@ private fun VitalSignsCard( bloodPressureS = 0, bloodPressureD = 0, temperature = 36.6f, - mood = "", energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = "" ) @@ -305,9 +301,7 @@ private fun VitalSignsCard( bloodPressureS = 0, bloodPressureD = 0, temperature = 36.6f, - mood = "", energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = "" ) @@ -333,9 +327,7 @@ private fun VitalSignsCard( bloodPressureS = 0, bloodPressureD = 0, temperature = 36.6f, - mood = "", energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = "" ) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthViewModel.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthViewModel.kt index debc9a0..d773155 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthViewModel.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/health/HealthViewModel.kt @@ -37,30 +37,41 @@ class HealthViewModel @Inject constructor( _uiState.value = _uiState.value.copy(isLoading = true) try { + // TODO: Временно используем заглушки, пока не добавим методы в repository + _uiState.value = _uiState.value.copy( + todayRecord = null, + lastUpdateDate = null, + todaySymptoms = emptyList(), + todayNotes = "", + recentRecords = emptyList(), + weeklyWeights = emptyMap(), + isLoading = false + ) + // Загружаем данные о здоровье за сегодня - repository.getTodayHealthData().collect { todayRecord: HealthRecordEntity? -> - _uiState.value = _uiState.value.copy( - todayRecord = todayRecord, - lastUpdateDate = todayRecord?.date, - todaySymptoms = todayRecord?.symptoms ?: emptyList(), - todayNotes = todayRecord?.notes ?: "", - isLoading = false - ) - } + // repository.getTodayHealthData().collect { todayRecord: HealthRecordEntity? -> + // _uiState.value = _uiState.value.copy( + // todayRecord = todayRecord, + // lastUpdateDate = todayRecord?.date, + // todaySymptoms = todayRecord?.symptoms ?: emptyList(), + // todayNotes = todayRecord?.notes ?: "", + // isLoading = false + // ) + // } // Загружаем недельные данные веса - repository.getAllHealthRecords().collect { records: List -> - val weightsMap = records - .filter { it.weight != null && it.weight > 0f } - .groupBy { it.date } - .mapValues { entry -> entry.value.last().weight ?: 0f } - _uiState.value = _uiState.value.copy(weeklyWeights = weightsMap) - } + // repository.getAllHealthRecords().collect { records: List -> + // val weightsMap = records + // .filter { it.weight != null && it.weight > 0f } + // .groupBy { it.date } + // .mapValues { entry -> entry.value.last().weight ?: 0f } + // _uiState.value = _uiState.value.copy(weeklyWeights = weightsMap) + // } // Загружаем последние записи - repository.getRecentHealthRecords().collect { records: List -> - _uiState.value = _uiState.value.copy(recentRecords = records) - } + // repository.getRecentHealthRecords().collect { records: List -> + // _uiState.value = _uiState.value.copy(recentRecords = records) + // } } catch (e: Exception) { _uiState.value = _uiState.value.copy( @@ -91,42 +102,13 @@ class HealthViewModel @Inject constructor( bloodPressureS = bpSystolic, bloodPressureD = bpDiastolic, temperature = temperature, - mood = "", energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = "" ) } - repository.saveHealthRecord(updatedRecord) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun updateMood(mood: String) { - viewModelScope.launch { - try { - val currentRecord = _uiState.value.todayRecord - val updatedRecord = if (currentRecord != null) { - currentRecord.copy(mood = mood) - } else { - HealthRecordEntity( - date = LocalDate.now(), - weight = 0f, - heartRate = 0, - bloodPressureS = 0, - bloodPressureD = 0, - temperature = 36.6f, - mood = mood, - energyLevel = 5, - stressLevel = 5, - symptoms = emptyList(), - notes = "" - ) - } - repository.saveHealthRecord(updatedRecord) + // TODO: Добавить метод saveHealthRecord в repository + // repository.saveHealthRecord(updatedRecord) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } @@ -142,47 +124,18 @@ class HealthViewModel @Inject constructor( } else { HealthRecordEntity( date = LocalDate.now(), - weight = 0f, - heartRate = 0, - bloodPressureS = 0, - bloodPressureD = 0, - temperature = 36.6f, - mood = "", + weight = null, + heartRate = null, + bloodPressureS = null, + bloodPressureD = null, + temperature = null, energyLevel = energy, - stressLevel = 5, symptoms = emptyList(), notes = "" ) } - repository.saveHealthRecord(updatedRecord) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun updateStressLevel(stress: Int) { - viewModelScope.launch { - try { - val currentRecord = _uiState.value.todayRecord - val updatedRecord = if (currentRecord != null) { - currentRecord.copy(stressLevel = stress) - } else { - HealthRecordEntity( - date = LocalDate.now(), - weight = 0f, - heartRate = 0, - bloodPressureS = 0, - bloodPressureD = 0, - temperature = 36.6f, - mood = "", - energyLevel = 5, - stressLevel = stress, - symptoms = emptyList(), - notes = "" - ) - } - repository.saveHealthRecord(updatedRecord) + // TODO: Добавить метод saveHealthRecord в repository + // repository.saveHealthRecord(updatedRecord) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } @@ -199,19 +152,18 @@ class HealthViewModel @Inject constructor( } else { HealthRecordEntity( date = LocalDate.now(), - weight = 0f, - heartRate = 0, - bloodPressureS = 0, - bloodPressureD = 0, - temperature = 36.6f, - mood = "", + weight = null, + heartRate = null, + bloodPressureS = null, + bloodPressureD = null, + temperature = null, energyLevel = 5, - stressLevel = 5, symptoms = symptoms, notes = "" ) } - repository.saveHealthRecord(updatedRecord) + // TODO: Добавить метод saveHealthRecord в repository + // repository.saveHealthRecord(updatedRecord) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } @@ -228,19 +180,18 @@ class HealthViewModel @Inject constructor( } else { HealthRecordEntity( date = LocalDate.now(), - weight = 0f, - heartRate = 0, - bloodPressureS = 0, - bloodPressureD = 0, - temperature = 36.6f, - mood = "", + weight = null, + heartRate = null, + bloodPressureS = null, + bloodPressureD = null, + temperature = null, energyLevel = 5, - stressLevel = 5, symptoms = emptyList(), notes = notes ) } - repository.saveHealthRecord(updatedRecord) + // TODO: Добавить метод saveHealthRecord в repository + // repository.saveHealthRecord(updatedRecord) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } @@ -250,7 +201,8 @@ class HealthViewModel @Inject constructor( fun deleteHealthRecord(record: HealthRecordEntity) { viewModelScope.launch { try { - repository.deleteHealthRecord(record.id) + // TODO: Добавить метод deleteHealthRecord в repository + // repository.deleteHealthRecord(record.id) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodScreen.kt deleted file mode 100644 index 0bf2401..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodScreen.kt +++ /dev/null @@ -1,258 +0,0 @@ -package kr.smartsoltech.wellshe.ui.mood - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.ModeNight -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import kr.smartsoltech.wellshe.ui.components.InfoCard -import kr.smartsoltech.wellshe.ui.components.StatCard -import kr.smartsoltech.wellshe.ui.theme.MoodTabColor -import kr.smartsoltech.wellshe.ui.theme.WellSheTheme - -/** - * Экран "Настроение" для отслеживания сна и эмоционального состояния - */ -@Composable -fun MoodScreen( - modifier: Modifier = Modifier -) { - val scrollState = rememberScrollState() - - Column( - modifier = modifier - .fillMaxSize() - .padding(16.dp) - .verticalScroll(scrollState), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Статистические карточки - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - StatCard( - title = "Сон", - value = "7.2 ч", - tone = Color(0xFF673AB7), // Фиолетовый для сна - modifier = Modifier.weight(1f) - ) - - StatCard( - title = "Стресс", - value = "3/10", - tone = Color(0xFFE91E63), // Розовый для стресса - modifier = Modifier.weight(1f) - ) - } - - // Карточка дневника - Card( - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.extraLarge, - colors = CardDefaults.cardColors( - containerColor = MoodTabColor.copy(alpha = 0.3f) - ) - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Заголовок - Text( - text = "Дневник", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold - ) - - // Содержимое дневника - Text( - text = "Сегодня было продуктивно, немного тревоги перед встречей. Выполнила все запланированные задачи, чувствую удовлетворение от проделанной работы.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - // Кнопки действий - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - TextButton(onClick = { /* TODO */ }) { - Text("Редактировать") - } - - TextButton(onClick = { /* TODO */ }) { - Text("Добавить запись") - } - } - } - } - - // Карточка сна - Card( - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.extraLarge, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) - ) - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Заголовок с иконкой - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - imageVector = Icons.Default.ModeNight, - contentDescription = null, - tint = Color(0xFF673AB7) - ) - Text( - text = "Качество сна", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - } - - // Оценка сна - Column( - modifier = Modifier.padding(vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text("Продолжительность") - Text("7.2 часа", fontWeight = FontWeight.SemiBold) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text("Качество") - Text("Хорошее", fontWeight = FontWeight.SemiBold) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text("Пробуждения") - Text("1 раз", fontWeight = FontWeight.SemiBold) - } - } - - // Кнопка добавления записи - OutlinedButton( - onClick = { /* TODO */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Записать сон") - } - } - } - - // Карточка эмоций - Card( - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.extraLarge, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) - ) - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Заголовок с иконкой - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - imageVector = Icons.Default.Favorite, - contentDescription = null, - tint = Color(0xFFE91E63) - ) - Text( - text = "Эмоциональное состояние", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - } - - // Текущее настроение - Text( - text = "Текущее настроение: Спокойствие, удовлетворение", - style = MaterialTheme.typography.bodyMedium - ) - - // Кнопки эмоций - EmojiButtonsRow() - } - } - - // Карточка рекомендаций - InfoCard( - title = "Рекомендации", - content = "Стабильный сон и низкий уровень стресса положительно влияют на ваш цикл. Рекомендуется поддерживать текущий режим для гормонального баланса." - ) - } -} - -/** - * Строка кнопок с эмодзи для выбора эмоций - */ -@Composable -fun EmojiButtonsRow() { - val emojis = listOf("😊", "😌", "🙂", "😐", "😔", "😢", "😡") - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - emojis.forEach { emoji -> - OutlinedButton( - onClick = { /* TODO */ }, - contentPadding = PaddingValues(12.dp), - modifier = Modifier.size(44.dp), - shape = MaterialTheme.shapes.medium, - colors = ButtonDefaults.outlinedButtonColors( - contentColor = MaterialTheme.colorScheme.onSurface - ) - ) { - Text( - text = emoji, - style = MaterialTheme.typography.titleMedium - ) - } - } - } -} - -@Preview(showBackground = true) -@Composable -fun MoodScreenPreview() { - WellSheTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - MoodScreen() - } - } -} diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodViewModel.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodViewModel.kt deleted file mode 100644 index ef4255d..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/mood/MoodViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -package kr.smartsoltech.wellshe.ui.mood - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import javax.inject.Inject - -@HiltViewModel -class MoodViewModel @Inject constructor() : ViewModel() { - // Данные для экрана настроения - private val _sleepHours = MutableStateFlow(7.2f) - val sleepHours: StateFlow = _sleepHours.asStateFlow() - - private val _stressLevel = MutableStateFlow(3) - val stressLevel: StateFlow = _stressLevel.asStateFlow() - - private val _journalEntry = MutableStateFlow("Сегодня было продуктивно, немного тревоги перед встречей.") - val journalEntry: StateFlow = _journalEntry.asStateFlow() - - fun updateSleepHours(hours: Float) { - _sleepHours.value = hours - } - - fun updateStressLevel(level: Int) { - _stressLevel.value = level - } - - fun updateJournalEntry(entry: String) { - _journalEntry.value = entry - } -} diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/AppNavGraph.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/AppNavGraph.kt index d1a1411..8e0c78c 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/AppNavGraph.kt @@ -8,12 +8,11 @@ import androidx.navigation.compose.composable import kr.smartsoltech.wellshe.ui.analytics.AnalyticsScreen import kr.smartsoltech.wellshe.ui.body.BodyScreen import kr.smartsoltech.wellshe.ui.cycle.CycleScreen -import kr.smartsoltech.wellshe.ui.mood.MoodScreen +import kr.smartsoltech.wellshe.ui.emergency.EmergencyScreen import kr.smartsoltech.wellshe.ui.profile.ProfileScreen import kr.smartsoltech.wellshe.ui.auth.AuthViewModel import kr.smartsoltech.wellshe.ui.auth.compose.LoginScreen import kr.smartsoltech.wellshe.ui.auth.compose.RegisterScreen -import kr.smartsoltech.wellshe.ui.emergency.EmergencyScreen @Composable fun AppNavGraph( @@ -57,15 +56,6 @@ fun AppNavGraph( ) } - // Экран экстренной помощи - composable("emergency") { - EmergencyScreen( - onNavigateBack = { - navController.popBackStack() - } - ) - } - // Существующие экраны composable(BottomNavItem.Cycle.route) { CycleScreen( @@ -91,8 +81,12 @@ fun AppNavGraph( BodyScreen() } - composable(BottomNavItem.Mood.route) { - MoodScreen() + composable(BottomNavItem.Emergency.route) { + EmergencyScreen( + onNavigateBack = { + navController.popBackStack() + } + ) } composable(BottomNavItem.Analytics.route) { diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavItem.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavItem.kt index 516f330..1157cde 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavItem.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavItem.kt @@ -2,14 +2,14 @@ package kr.smartsoltech.wellshe.ui.navigation import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BarChart -import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Emergency import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.WaterDrop import androidx.compose.material.icons.filled.WbSunny import androidx.compose.ui.graphics.vector.ImageVector /** - * Модель навигационного элемента для нижней панели навигац��и + * Модель навигационного элемента для нижней панели навигации */ sealed class BottomNavItem( val route: String, @@ -28,10 +28,10 @@ sealed class BottomNavItem( icon = Icons.Default.WaterDrop ) - data object Mood : BottomNavItem( - route = "mood", - title = "Настроение", - icon = Icons.Default.Favorite + data object Emergency : BottomNavItem( + route = "emergency", + title = "Экстренное", + icon = Icons.Default.Emergency ) data object Analytics : BottomNavItem( @@ -47,6 +47,6 @@ sealed class BottomNavItem( ) companion object { - val items = listOf(Cycle, Body, Mood, Analytics, Profile) + val items = listOf(Cycle, Body, Emergency, Analytics, Profile) } } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavigation.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavigation.kt index 36d9e57..97d49a3 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavigation.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/navigation/BottomNavigation.kt @@ -49,7 +49,7 @@ fun BottomNavigation( val backgroundColor = when (item) { BottomNavItem.Cycle -> CycleTabColor BottomNavItem.Body -> BodyTabColor - BottomNavItem.Mood -> MoodTabColor + BottomNavItem.Emergency -> ErrorRed BottomNavItem.Analytics -> AnalyticsTabColor BottomNavItem.Profile -> ProfileTabColor } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsScreen.kt index d6282d5..37618e8 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsScreen.kt @@ -59,10 +59,8 @@ fun SettingsScreen( NotificationSettingsCard( isWaterReminderEnabled = uiState.isWaterReminderEnabled, isCycleReminderEnabled = uiState.isCycleReminderEnabled, - isSleepReminderEnabled = uiState.isSleepReminderEnabled, - onWaterReminderToggle = viewModel::toggleWaterReminder, - onCycleReminderToggle = viewModel::toggleCycleReminder, - onSleepReminderToggle = viewModel::toggleSleepReminder + onWaterReminderToggle = viewModel::updateWaterReminder, + onCycleReminderToggle = viewModel::updateCycleReminder ) } @@ -79,24 +77,22 @@ fun SettingsScreen( GoalsSettingsCard( waterGoal = uiState.waterGoal, stepsGoal = uiState.stepsGoal, - sleepGoal = uiState.sleepGoal, onWaterGoalChange = viewModel::updateWaterGoal, - onStepsGoalChange = viewModel::updateStepsGoal, - onSleepGoalChange = viewModel::updateSleepGoal + onStepsGoalChange = viewModel::updateStepsGoal ) } item { AppearanceSettingsCard( isDarkTheme = uiState.isDarkTheme, - onThemeToggle = viewModel::toggleTheme + onThemeToggle = viewModel::updateTheme ) } item { DataManagementCard( onExportData = viewModel::exportData, - onImportData = viewModel::importData, + onImportData = { viewModel.importData(it) }, onClearData = viewModel::clearAllData ) } @@ -155,10 +151,8 @@ private fun SettingsHeader( private fun NotificationSettingsCard( isWaterReminderEnabled: Boolean, isCycleReminderEnabled: Boolean, - isSleepReminderEnabled: Boolean, onWaterReminderToggle: (Boolean) -> Unit, onCycleReminderToggle: (Boolean) -> Unit, - onSleepReminderToggle: (Boolean) -> Unit, modifier: Modifier = Modifier ) { SettingsCard( @@ -181,15 +175,6 @@ private fun NotificationSettingsCard( isChecked = isCycleReminderEnabled, onCheckedChange = onCycleReminderToggle ) - - Spacer(modifier = Modifier.height(16.dp)) - - SettingsSwitchItem( - title = "Напоминания о сне", - subtitle = "Уведомления о режиме сна", - isChecked = isSleepReminderEnabled, - onCheckedChange = onSleepReminderToggle - ) } } @@ -234,10 +219,8 @@ private fun CycleSettingsCard( private fun GoalsSettingsCard( waterGoal: Float, stepsGoal: Int, - sleepGoal: Float, onWaterGoalChange: (Float) -> Unit, onStepsGoalChange: (Int) -> Unit, - onSleepGoalChange: (Float) -> Unit, modifier: Modifier = Modifier ) { SettingsCard( @@ -266,18 +249,6 @@ private fun GoalsSettingsCard( }, suffix = "шагов" ) - - Spacer(modifier = Modifier.height(20.dp)) - - SettingsDecimalField( - title = "Цель по сну", - subtitle = "Количество часов сна (6-10 часов)", - value = sleepGoal, - onValueChange = { value -> - if (value in 6.0f..10.0f) onSleepGoalChange(value) - }, - suffix = "часов" - ) } } @@ -304,7 +275,7 @@ private fun AppearanceSettingsCard( @Composable private fun DataManagementCard( onExportData: () -> Unit, - onImportData: () -> Unit, + onImportData: (String) -> Unit, onClearData: () -> Unit, modifier: Modifier = Modifier ) { @@ -326,7 +297,7 @@ private fun DataManagementCard( title = "Импорт данных", subtitle = "Загрузить данные из файла", icon = Icons.Default.Upload, - onClick = onImportData + onClick = { onImportData("") } ) Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsViewModel.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsViewModel.kt index 43f4402..813dbd9 100644 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/kr/smartsoltech/wellshe/ui/settings/SettingsViewModel.kt @@ -14,12 +14,10 @@ import javax.inject.Inject data class SettingsUiState( val isWaterReminderEnabled: Boolean = true, val isCycleReminderEnabled: Boolean = true, - val isSleepReminderEnabled: Boolean = true, val cycleLength: Int = 28, val periodLength: Int = 5, val waterGoal: Float = 2.5f, val stepsGoal: Int = 10000, - val sleepGoal: Float = 8.0f, val isDarkTheme: Boolean = false, val isLoading: Boolean = false, val error: String? = null @@ -38,23 +36,17 @@ class SettingsViewModel @Inject constructor( _uiState.value = _uiState.value.copy(isLoading = true) try { - repository.getSettings().catch { e -> + // TODO: Временно используем заглушки до реализации методов в repository + repository.getAppSettings().catch { e -> _uiState.value = _uiState.value.copy( isLoading = false, error = e.message ) }.collect { settings -> _uiState.value = _uiState.value.copy( - isWaterReminderEnabled = settings.isWaterReminderEnabled, - isCycleReminderEnabled = settings.isCycleReminderEnabled, - isSleepReminderEnabled = settings.isSleepReminderEnabled, - cycleLength = settings.cycleLength, - periodLength = settings.periodLength, - waterGoal = settings.waterGoal, - stepsGoal = settings.stepsGoal, - sleepGoal = settings.sleepGoal, - isDarkTheme = settings.isDarkTheme, - isLoading = false + isDarkTheme = settings.darkModeEnabled, + isLoading = false, + error = null ) } } catch (e: Exception) { @@ -66,11 +58,11 @@ class SettingsViewModel @Inject constructor( } } - // Уведомления - fun toggleWaterReminder(enabled: Boolean) { + // Обновление настроек уведомлений + fun updateWaterReminder(enabled: Boolean) { viewModelScope.launch { try { - repository.updateWaterReminderSetting(enabled) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(isWaterReminderEnabled = enabled) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -78,10 +70,10 @@ class SettingsViewModel @Inject constructor( } } - fun toggleCycleReminder(enabled: Boolean) { + fun updateCycleReminder(enabled: Boolean) { viewModelScope.launch { try { - repository.updateCycleReminderSetting(enabled) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(isCycleReminderEnabled = enabled) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -89,23 +81,12 @@ class SettingsViewModel @Inject constructor( } } - fun toggleSleepReminder(enabled: Boolean) { - viewModelScope.launch { - try { - repository.updateSleepReminderSetting(enabled) - _uiState.value = _uiState.value.copy(isSleepReminderEnabled = enabled) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - // Настройки цикла + // Обновление параметров цикла fun updateCycleLength(length: Int) { if (length in 21..35) { viewModelScope.launch { try { - repository.updateCycleLength(length) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(cycleLength = length) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -115,10 +96,10 @@ class SettingsViewModel @Inject constructor( } fun updatePeriodLength(length: Int) { - if (length in 3..8) { + if (length in 3..7) { viewModelScope.launch { try { - repository.updatePeriodLength(length) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(periodLength = length) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -127,12 +108,12 @@ class SettingsViewModel @Inject constructor( } } - // Цели + // Обновление целей fun updateWaterGoal(goal: Float) { - if (goal in 1.5f..4.0f) { + if (goal > 0) { viewModelScope.launch { try { - repository.updateWaterGoal(goal) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(waterGoal = goal) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -142,10 +123,10 @@ class SettingsViewModel @Inject constructor( } fun updateStepsGoal(goal: Int) { - if (goal in 5000..20000) { + if (goal > 0) { viewModelScope.launch { try { - repository.updateStepsGoal(goal) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(stepsGoal = goal) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -154,24 +135,11 @@ class SettingsViewModel @Inject constructor( } } - fun updateSleepGoal(goal: Float) { - if (goal in 6.0f..10.0f) { - viewModelScope.launch { - try { - repository.updateSleepGoal(goal) - _uiState.value = _uiState.value.copy(sleepGoal = goal) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - } - - // Внешний вид - fun toggleTheme(isDark: Boolean) { + // Обновление темы + fun updateTheme(isDark: Boolean) { viewModelScope.launch { try { - repository.updateThemeSetting(isDark) + // TODO: Реализовать через repository _uiState.value = _uiState.value.copy(isDarkTheme = isDark) } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) @@ -179,35 +147,33 @@ class SettingsViewModel @Inject constructor( } } - // Управление данными + // Экспорт данных fun exportData() { viewModelScope.launch { try { - repository.exportUserData() - // Показать сообщение об успехе + // TODO: Реализовать экспорт данных } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } } } - fun importData() { + // Импорт данных + fun importData(data: String) { viewModelScope.launch { try { - repository.importUserData() - loadSettings() // Перезагрузить настройки + // TODO: Реализовать импорт данных } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } } } + // Очистка данных fun clearAllData() { viewModelScope.launch { try { - repository.clearAllUserData() - // Сбросить на дефолтные значения - _uiState.value = SettingsUiState() + // TODO: Реализовать очистку данных } catch (e: Exception) { _uiState.value = _uiState.value.copy(error = e.message) } diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepScreen.kt deleted file mode 100644 index 2c6d5fd..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepScreen.kt +++ /dev/null @@ -1,875 +0,0 @@ -package kr.smartsoltech.wellshe.ui.sleep - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity -import kr.smartsoltech.wellshe.ui.theme.* -import java.time.LocalDate -import java.time.LocalTime -import java.time.format.DateTimeFormatter -import kotlin.math.cos -import kotlin.math.sin - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SleepScreen( - modifier: Modifier = Modifier, - viewModel: SleepViewModel = hiltViewModel() -) { - val uiState by viewModel.uiState.collectAsState() - - LaunchedEffect(Unit) { - viewModel.loadSleepData() - } - - LazyColumn( - modifier = modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color(0xFF3F51B5).copy(alpha = 0.2f), - NeutralWhite - ) - ) - ), - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - item { - SleepOverviewCard( - lastNightSleep = uiState.lastNightSleep, - sleepGoal = uiState.sleepGoal, - weeklyAverage = uiState.weeklyAverage - ) - } - - item { - SleepTrackerCard( - isTracking = uiState.isTracking, - currentSleep = uiState.currentSleep, - onStartTracking = viewModel::startSleepTracking, - onStopTracking = viewModel::stopSleepTracking - ) - } - - item { - SleepQualityCard( - todayQuality = uiState.todayQuality, - isEditMode = uiState.isEditMode, - onQualityUpdate = viewModel::updateSleepQuality, - onToggleEdit = viewModel::toggleEditMode - ) - } - - item { - WeeklySleepChart( - weeklyData = uiState.weeklyData, - sleepGoal = uiState.sleepGoal - ) - } - - item { - SleepInsightsCard( - insights = uiState.insights - ) - } - - item { - SleepTipsCard() - } - - item { - RecentSleepLogsCard( - sleepLogs = uiState.recentLogs, - onLogClick = { /* TODO: Navigate to sleep log details */ } - ) - } - - item { - Spacer(modifier = Modifier.height(80.dp)) - } - } - - if (uiState.error != null) { - LaunchedEffect(uiState.error) { - viewModel.clearError() - } - } -} - -@Composable -private fun SleepOverviewCard( - lastNightSleep: SleepLogEntity?, - sleepGoal: Float, - weeklyAverage: Float, - modifier: Modifier = Modifier -) { - val sleepDuration = lastNightSleep?.duration ?: 0f - val progress by animateFloatAsState( - targetValue = if (sleepGoal > 0) (sleepDuration / sleepGoal).coerceIn(0f, 1f) else 0f, - animationSpec = tween(durationMillis = 1000) - ) - - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(20.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Сон прошлой ночи", - style = MaterialTheme.typography.headlineSmall.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - - Spacer(modifier = Modifier.height(24.dp)) - - Box( - modifier = Modifier.size(200.dp), - contentAlignment = Alignment.Center - ) { - SleepProgressIndicator( - progress = progress, - modifier = Modifier.fillMaxSize() - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = Color(0xFF3F51B5), - modifier = Modifier.size(32.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = if (sleepDuration > 0) "%.1f ч".format(sleepDuration) else "—", - style = MaterialTheme.typography.headlineLarge.copy( - fontWeight = FontWeight.Bold, - color = Color(0xFF3F51B5) - ) - ) - Text( - text = "из %.1f ч".format(sleepGoal), - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - if (sleepDuration > 0) { - Text( - text = "${(progress * 100).toInt()}% от цели", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - color = Color(0xFF3F51B5) - ) - ) - } - } - } - - Spacer(modifier = Modifier.height(20.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - SleepStatItem( - icon = Icons.Default.AccessTime, - label = "Время сна", - value = lastNightSleep?.bedTime ?: "—", - color = Color(0xFF9C27B0) - ) - - SleepStatItem( - icon = Icons.Default.WbSunny, - label = "Подъем", - value = lastNightSleep?.wakeTime ?: "—", - color = Color(0xFFFF9800) - ) - - SleepStatItem( - icon = Icons.Default.TrendingUp, - label = "Средний сон", - value = if (weeklyAverage > 0) "%.1f ч".format(weeklyAverage) else "—", - color = Color(0xFF4CAF50) - ) - } - } - } -} - -@Composable -private fun SleepProgressIndicator( - progress: Float, - modifier: Modifier = Modifier -) { - Canvas(modifier = modifier) { - val center = this.center - val radius = size.minDimension / 2 - 20.dp.toPx() - val strokeWidth = 12.dp.toPx() - - // Фон круга - drawCircle( - color = Color(0xFFE8EAF6), - radius = radius, - center = center, - style = Stroke(width = strokeWidth) - ) - - // Прогресс-дуга - val sweepAngle = 360f * progress - drawArc( - color = Color(0xFF3F51B5), - startAngle = -90f, - sweepAngle = sweepAngle, - useCenter = false, - style = Stroke(width = strokeWidth, cap = StrokeCap.Round), - topLeft = Offset(center.x - radius, center.y - radius), - size = Size(radius * 2, radius * 2) - ) - } -} - -@Composable -private fun SleepTrackerCard( - isTracking: Boolean, - currentSleep: SleepLogEntity?, - onStartTracking: () -> Unit, - onStopTracking: () -> Unit, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Трекер сна", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ), - modifier = Modifier.padding(bottom = 16.dp) - ) - - if (isTracking) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = Color(0xFF3F51B5), - modifier = Modifier.size(48.dp) - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Отслеживание сна активно", - style = MaterialTheme.typography.titleMedium.copy( - color = TextPrimary - ) - ) - - Text( - text = "Начало: ${currentSleep?.bedTime ?: "—"}", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = onStopTracking, - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFFFF5722) - ), - shape = RoundedCornerShape(24.dp) - ) { - Icon( - imageVector = Icons.Default.Stop, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text("Завершить сон") - } - } - } else { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Default.Hotel, - contentDescription = null, - tint = Color(0xFF9E9E9E), - modifier = Modifier.size(48.dp) - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Готовы ко сну?", - style = MaterialTheme.typography.titleMedium.copy( - color = TextPrimary - ) - ) - - Text( - text = "Нажмите кнопку, когда ложитесь спать", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ), - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = onStartTracking, - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF3F51B5) - ), - shape = RoundedCornerShape(24.dp) - ) { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text("Начать отслеживание") - } - } - } - } - } -} - -@Composable -private fun SleepQualityCard( - todayQuality: String, - isEditMode: Boolean, - onQualityUpdate: (String) -> Unit, - onToggleEdit: () -> Unit, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Качество сна", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - - IconButton(onClick = onToggleEdit) { - Icon( - imageVector = if (isEditMode) Icons.Default.Check else Icons.Default.Edit, - contentDescription = if (isEditMode) "Сохранить" else "Редактировать", - tint = Color(0xFF3F51B5) - ) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - if (isEditMode) { - val qualities = listOf("Отличное", "Хорошее", "Удовлетворительное", "Плохое") - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(qualities) { quality -> - FilterChip( - onClick = { onQualityUpdate(quality) }, - label = { Text(quality) }, - selected = todayQuality == quality, - colors = FilterChipDefaults.filterChipColors( - selectedContainerColor = Color(0xFF3F51B5), - selectedLabelColor = NeutralWhite - ) - ) - } - } - } else { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - val qualityIcon = when (todayQuality) { - "Отличное" -> Icons.Default.SentimentVerySatisfied - "Хорошее" -> Icons.Default.SentimentSatisfied - "Удовлетворительное" -> Icons.Default.SentimentNeutral - "Плохое" -> Icons.Default.SentimentVeryDissatisfied - else -> Icons.Default.SentimentNeutral - } - - val qualityColor = when (todayQuality) { - "Отличное" -> Color(0xFF4CAF50) - "Хорошее" -> Color(0xFF8BC34A) - "Удовлетворительное" -> Color(0xFFFF9800) - "Плохое" -> Color(0xFFE91E63) - else -> Color(0xFF9E9E9E) - } - - Icon( - imageVector = qualityIcon, - contentDescription = null, - tint = qualityColor, - modifier = Modifier.size(32.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Text( - text = todayQuality.ifEmpty { "Не оценено" }, - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - } - } - } - } -} - -@Composable -private fun WeeklySleepChart( - weeklyData: Map, - sleepGoal: Float, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Text( - text = "Сон за неделю", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ), - modifier = Modifier.padding(bottom = 16.dp) - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - weeklyData.entries.toList().takeLast(7).forEach { (date, duration) -> - WeeklySleepBar( - date = date, - duration = duration, - goal = sleepGoal, - modifier = Modifier.weight(1f) - ) - } - } - } - } -} - -@Composable -private fun WeeklySleepBar( - date: LocalDate, - duration: Float, - goal: Float, - modifier: Modifier = Modifier -) { - val progress = if (goal > 0) (duration / goal).coerceIn(0f, 1f) else 0f - val animatedProgress by animateFloatAsState( - targetValue = progress, - animationSpec = tween(durationMillis = 1000) - ) - - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = date.dayOfWeek.name.take(3), - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Box( - modifier = Modifier - .width(24.dp) - .height(80.dp) - .clip(RoundedCornerShape(12.dp)) - .background(Color(0xFFE8EAF6)) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(animatedProgress) - .clip(RoundedCornerShape(12.dp)) - .background( - Brush.verticalGradient( - colors = listOf( - Color(0xFF7986CB), - Color(0xFF3F51B5) - ) - ) - ) - .align(Alignment.BottomCenter) - ) - } - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = if (duration > 0) "%.1f".format(duration) else "—", - style = MaterialTheme.typography.bodySmall.copy( - color = TextPrimary, - fontWeight = FontWeight.Medium - ) - ) - } -} - -@Composable -private fun SleepInsightsCard( - insights: List, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors( - containerColor = Color(0xFFE8EAF6) - ), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 16.dp) - ) { - Icon( - imageVector = Icons.Default.Analytics, - contentDescription = null, - tint = Color(0xFF3F51B5), - modifier = Modifier.size(24.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Text( - text = "Анализ сна", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - } - - if (insights.isEmpty()) { - Text( - text = "Недостаточно данных для анализа. Отслеживайте сон несколько дней для получения персональных рекомендаций.", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextPrimary - ) - ) - } else { - insights.forEach { insight -> - Row( - modifier = Modifier.padding(vertical = 4.dp) - ) { - Icon( - imageVector = Icons.Default.Circle, - contentDescription = null, - tint = Color(0xFF3F51B5), - modifier = Modifier.size(8.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Text( - text = insight, - style = MaterialTheme.typography.bodyMedium.copy( - color = TextPrimary - ) - ) - } - } - } - } - } -} - -@Composable -private fun SleepTipsCard( - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors( - containerColor = Color(0xFFF3E5F5) - ), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 12.dp) - ) { - Icon( - imageVector = Icons.Default.Lightbulb, - contentDescription = null, - tint = Color(0xFF9C27B0), - modifier = Modifier.size(24.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Text( - text = "Совет для лучшего сна", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - } - - Text( - text = "Создайте ритуал перед сном: выключите экраны за час до сна, примите теплую ванну или выпейте травяной чай. Регулярный режим поможет организму подготовиться ко сну.", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextPrimary - ) - ) - } - } -} - -@Composable -private fun RecentSleepLogsCard( - sleepLogs: List, - onLogClick: (SleepLogEntity) -> Unit, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Text( - text = "Последние записи сна", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ), - modifier = Modifier.padding(bottom = 16.dp) - ) - - if (sleepLogs.isEmpty()) { - Text( - text = "Пока нет записей о сне", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - } else { - sleepLogs.take(3).forEach { log -> - SleepLogItem( - sleepLog = log, - onClick = { onLogClick(log) } - ) - if (log != sleepLogs.last()) { - Spacer(modifier = Modifier.height(12.dp)) - } - } - } - } - } -} - -@Composable -private fun SleepLogItem( - sleepLog: SleepLogEntity, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - Row( - modifier = modifier - .fillMaxWidth() - .clickable { onClick() } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = Color(0xFF3F51B5), - modifier = Modifier.size(32.dp) - ) - - Spacer(modifier = Modifier.width(16.dp)) - - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = sleepLog.date.format(DateTimeFormatter.ofPattern("dd MMMM yyyy")), - style = MaterialTheme.typography.bodyLarge.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - Text( - text = "${sleepLog.bedTime} - ${sleepLog.wakeTime} (%.1f ч)".format(sleepLog.duration), - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - } - - Text( - text = sleepLog.quality, - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Icon( - imageVector = Icons.Default.ChevronRight, - contentDescription = "Просмотреть", - tint = TextSecondary, - modifier = Modifier.size(20.dp) - ) - } -} - -@Composable -private fun SleepStatItem( - icon: ImageVector, - label: String, - value: String, - color: Color, - modifier: Modifier = Modifier -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = color, - modifier = Modifier.size(24.dp) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = label, - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ), - textAlign = TextAlign.Center - ) - - Text( - text = value, - style = MaterialTheme.typography.bodyMedium.copy( - color = TextPrimary, - fontWeight = FontWeight.Bold - ), - textAlign = TextAlign.Center - ) - } -} diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepTrackingScreen.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepTrackingScreen.kt deleted file mode 100644 index 075dc1a..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepTrackingScreen.kt +++ /dev/null @@ -1,675 +0,0 @@ -package kr.smartsoltech.wellshe.ui.sleep - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity -import kr.smartsoltech.wellshe.ui.theme.* -import java.time.LocalDate -import java.time.LocalTime -import java.time.format.DateTimeFormatter - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SleepTrackingScreen( - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: SleepViewModel = hiltViewModel() -) { - val uiState by viewModel.uiState.collectAsState() - - LaunchedEffect(Unit) { - viewModel.loadSleepData() - } - - Column( - modifier = modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - AccentPurpleLight.copy(alpha = 0.2f), - NeutralWhite - ) - ) - ) - ) { - TopAppBar( - title = { - Text( - text = "Отслеживание сна", - style = MaterialTheme.typography.titleLarge.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - }, - navigationIcon = { - IconButton(onClick = onBackClick) { - Icon( - imageVector = Icons.Default.ArrowBack, - contentDescription = "Назад", - tint = TextPrimary - ) - } - }, - actions = { - IconButton(onClick = { viewModel.toggleEditMode() }) { - Icon( - imageVector = if (uiState.isEditMode) Icons.Default.Save else Icons.Default.Edit, - contentDescription = if (uiState.isEditMode) "Сохранить" else "Редактировать", - tint = AccentPurple - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = NeutralWhite.copy(alpha = 0.95f) - ) - ) - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - item { - TodaySleepCard( - uiState = uiState, - onUpdateSleep = { bedTime, wakeTime, quality, notes -> - // Создаем SleepLogEntity и передаем его в viewModel - val sleepLog = SleepLogEntity( - date = java.time.LocalDate.now(), - bedTime = bedTime, - wakeTime = wakeTime, - duration = calculateSleepDuration(bedTime, wakeTime), - quality = quality, - notes = notes - ) - viewModel.updateSleepRecord(sleepLog) - }, - onUpdateQuality = viewModel::updateSleepQuality, - onUpdateNotes = viewModel::updateNotes - ) - } - - item { - SleepStatsCard( - recentSleep = uiState.recentSleepLogs, - averageDuration = uiState.averageSleepDuration, - averageQuality = uiState.averageQuality - ) - } - - item { - SleepHistoryCard( - sleepLogs = uiState.recentSleepLogs, - onDeleteLog = viewModel::deleteSleepLog - ) - } - - item { - SleepTipsCard() - } - - item { - Spacer(modifier = Modifier.height(80.dp)) - } - } - } -} - -@Composable -private fun TodaySleepCard( - uiState: SleepUiState, - onUpdateSleep: (String, String, String, String) -> Unit, - onUpdateQuality: (String) -> Unit, - onUpdateNotes: (String) -> Unit, - modifier: Modifier = Modifier -) { - var bedTime by remember { mutableStateOf(uiState.todaySleep?.bedTime ?: "22:00") } - var wakeTime by remember { mutableStateOf(uiState.todaySleep?.wakeTime ?: "07:00") } - var notes by remember { mutableStateOf(uiState.todaySleep?.notes ?: "") } - - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(16.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Сон за ${LocalDate.now().format(DateTimeFormatter.ofPattern("d MMMM"))}", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.SemiBold, - color = TextPrimary - ) - ) - - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = AccentPurple, - modifier = Modifier.size(32.dp) - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - if (uiState.isEditMode) { - // Режим редактирования - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - OutlinedTextField( - value = bedTime, - onValueChange = { bedTime = it }, - label = { Text("Время сна") }, - placeholder = { Text("22:00") }, - modifier = Modifier.weight(1f) - ) - - OutlinedTextField( - value = wakeTime, - onValueChange = { wakeTime = it }, - label = { Text("Время пробуждения") }, - placeholder = { Text("07:00") }, - modifier = Modifier.weight(1f) - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Качество сна", - style = MaterialTheme.typography.bodyMedium.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(sleepQualities) { quality -> - FilterChip( - onClick = { onUpdateQuality(quality.key) }, - label = { - Row(verticalAlignment = Alignment.CenterVertically) { - Text(quality.emoji) - Spacer(modifier = Modifier.width(4.dp)) - Text(quality.name) - } - }, - selected = uiState.todaySleep?.quality == quality.key, - colors = FilterChipDefaults.filterChipColors( - selectedContainerColor = AccentPurpleLight, - selectedLabelColor = AccentPurple - ) - ) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - OutlinedTextField( - value = notes, - onValueChange = { - notes = it - onUpdateNotes(it) - }, - label = { Text("Заметки о сне") }, - placeholder = { Text("Как спалось, что снилось...") }, - modifier = Modifier.fillMaxWidth(), - minLines = 2 - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = { - onUpdateSleep(bedTime, wakeTime, uiState.todaySleep?.quality ?: "good", notes) - }, - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = AccentPurple) - ) { - Text("Сохранить данные сна") - } - } else { - // Режим просмотра - if (uiState.todaySleep != null) { - val sleep = uiState.todaySleep - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - SleepMetric( - label = "Время сна", - value = sleep.bedTime, - icon = Icons.Default.NightsStay - ) - - SleepMetric( - label = "Пробуждение", - value = sleep.wakeTime, - icon = Icons.Default.WbSunny - ) - - SleepMetric( - label = "Длительность", - value = "${sleep.duration}ч", - icon = Icons.Default.AccessTime - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - // Качество сна - val qualityData = sleepQualities.find { it.key == sleep.quality } ?: sleepQualities[2] - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Качество сна: ", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - Text( - text = qualityData.emoji, - style = MaterialTheme.typography.titleMedium - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = qualityData.name, - style = MaterialTheme.typography.bodyMedium.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - } - - if (sleep.notes.isNotEmpty()) { - Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "Заметки: ${sleep.notes}", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - } - } else { - Text( - text = "Данные о сне за сегодня не добавлены", - style = MaterialTheme.typography.bodyMedium.copy( - color = TextSecondary - ) - ) - } - } - } - } -} - -@Composable -private fun SleepMetric( - label: String, - value: String, - icon: androidx.compose.ui.graphics.vector.ImageVector, - modifier: Modifier = Modifier -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = AccentPurple, - modifier = Modifier.size(24.dp) - ) - - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = value, - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - - Text( - text = label, - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - } -} - -@Composable -private fun SleepStatsCard( - recentSleep: List, - averageDuration: Float, - averageQuality: String, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(12.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { - Text( - text = "Статистика за неделю", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.SemiBold, - color = TextPrimary - ) - ) - - Spacer(modifier = Modifier.height(12.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - SleepStatItem( - label = "Средняя длительность", - value = "${String.format("%.1f", averageDuration)}ч", - icon = Icons.Default.AccessTime - ) - - SleepStatItem( - label = "Записей сна", - value = "${recentSleep.size}", - icon = Icons.Default.EventNote - ) - - val qualityData = sleepQualities.find { it.key == averageQuality } ?: sleepQualities[2] - SleepStatItem( - label = "Среднее качество", - value = qualityData.emoji, - icon = Icons.Default.Star - ) - } - } - } -} - -@Composable -private fun SleepStatItem( - label: String, - value: String, - icon: androidx.compose.ui.graphics.vector.ImageVector, - modifier: Modifier = Modifier -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = AccentPurple, - modifier = Modifier.size(20.dp) - ) - - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = value, - style = MaterialTheme.typography.titleSmall.copy( - fontWeight = FontWeight.Bold, - color = TextPrimary - ) - ) - - Text( - text = label, - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - } -} - -@Composable -private fun SleepHistoryCard( - sleepLogs: List, - onDeleteLog: (kr.smartsoltech.wellshe.data.entity.SleepLogEntity) -> Unit, - modifier: Modifier = Modifier -) { - if (sleepLogs.isNotEmpty()) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = NeutralWhite), - shape = RoundedCornerShape(12.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { - Text( - text = "История сна", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.SemiBold, - color = TextPrimary - ) - ) - - Spacer(modifier = Modifier.height(12.dp)) - - sleepLogs.take(7).forEach { log -> - SleepHistoryItem( - log = log, - onDelete = { onDeleteLog(log) } - ) - } - } - } - } -} - -@Composable -private fun SleepHistoryItem( - log: kr.smartsoltech.wellshe.data.entity.SleepLogEntity, - onDelete: () -> Unit, - modifier: Modifier = Modifier -) { - Row( - modifier = modifier - .fillMaxWidth() - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Bedtime, - contentDescription = null, - tint = AccentPurple, - modifier = Modifier.size(20.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = log.date.format(DateTimeFormatter.ofPattern("d MMMM yyyy")), - style = MaterialTheme.typography.bodyMedium.copy( - fontWeight = FontWeight.Medium, - color = TextPrimary - ) - ) - - Text( - text = "${log.bedTime} - ${log.wakeTime} (${log.duration}ч)", - style = MaterialTheme.typography.bodySmall.copy( - color = TextSecondary - ) - ) - } - - val qualityData = sleepQualities.find { it.key == log.quality } ?: sleepQualities[2] - Text( - text = qualityData.emoji, - style = MaterialTheme.typography.titleMedium - ) - - Spacer(modifier = Modifier.width(8.dp)) - - IconButton( - onClick = onDelete, - modifier = Modifier.size(24.dp) - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Удалить", - tint = ErrorRed, - modifier = Modifier.size(16.dp) - ) - } - } -} - -@Composable -private fun SleepTipsCard( - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - colors = CardDefaults.cardColors(containerColor = AccentPurpleLight.copy(alpha = 0.3f)), - shape = RoundedCornerShape(12.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Lightbulb, - contentDescription = null, - tint = AccentPurple, - modifier = Modifier.size(24.dp) - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Text( - text = "Советы для лучшего сна", - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.SemiBold, - color = TextPrimary - ) - ) - } - - Spacer(modifier = Modifier.height(12.dp)) - - sleepTips.forEach { tip -> - Row( - modifier = Modifier.padding(vertical = 2.dp) - ) { - Text( - text = "• ", - style = MaterialTheme.typography.bodyMedium.copy( - color = AccentPurple, - fontWeight = FontWeight.Bold - ) - ) - Text( - text = tip, - style = MaterialTheme.typography.bodyMedium.copy( - color = TextPrimary - ) - ) - } - } - } - } -} - -// Данные для UI -private data class SleepQualityData(val key: String, val name: String, val emoji: String) - -private val sleepQualities = listOf( - SleepQualityData("poor", "Плохо", "😴"), - SleepQualityData("fair", "Нормально", "😐"), - SleepQualityData("good", "Хорошо", "😊"), - SleepQualityData("excellent", "Отлично", "😄") -) - -private val sleepTips = listOf( - "Ложитесь спать в одно и то же время", - "Избегайте кофеина за 6 часов до сна", - "Создайте прохладную и темную атмосферу", - "Ограничьте использование экранов перед сном", - "Проветривайте спальню перед сном", - "Делайте расслабляющие упражнения" -) - -// Вспомогательная функция для расчета продолжительности сна -private fun calculateSleepDuration(bedTime: String, wakeTime: String): Float { - return try { - val bedLocalTime = LocalTime.parse(bedTime) - val wakeLocalTime = LocalTime.parse(wakeTime) - - val duration = if (wakeLocalTime.isAfter(bedLocalTime)) { - // Сон в пределах одного дня - java.time.Duration.between(bedLocalTime, wakeLocalTime) - } else { - // Сон через полночь - val endOfDay = LocalTime.of(23, 59, 59) - val startOfDay = LocalTime.MIDNIGHT - val beforeMidnight = java.time.Duration.between(bedLocalTime, endOfDay) - val afterMidnight = java.time.Duration.between(startOfDay, wakeLocalTime) - beforeMidnight.plus(afterMidnight).plusMinutes(1) - } - - duration.toMinutes() / 60.0f - } catch (e: Exception) { - 8.0f // Возвращаем значение по умолчанию - } -} diff --git a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepViewModel.kt b/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepViewModel.kt deleted file mode 100644 index c0a7867..0000000 --- a/app/src/main/java/kr/smartsoltech/wellshe/ui/sleep/SleepViewModel.kt +++ /dev/null @@ -1,335 +0,0 @@ -package kr.smartsoltech.wellshe.ui.sleep - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity -import kr.smartsoltech.wellshe.data.repository.WellSheRepository -import java.time.LocalDate -import java.time.LocalTime -import java.time.format.DateTimeFormatter -import javax.inject.Inject - -data class SleepUiState( - val lastNightSleep: SleepLogEntity? = null, - val currentSleep: SleepLogEntity? = null, - val todaySleep: SleepLogEntity? = null, - val recentLogs: List = emptyList(), - val recentSleepLogs: List = emptyList(), // Добавляем недостающее поле - val averageSleepDuration: Float = 0f, // Добавляем недостающее поле - val averageQuality: String = "", // Добавляем недостающее поле - val weeklyData: Map = emptyMap(), - val sleepGoal: Float = 8.0f, - val weeklyAverage: Float = 0f, - val todayQuality: String = "", - val insights: List = emptyList(), - val isTracking: Boolean = false, - val isEditMode: Boolean = false, - val isLoading: Boolean = false, - val error: String? = null -) - -@HiltViewModel -class SleepViewModel @Inject constructor( - private val repository: WellSheRepository -) : ViewModel() { - - private val _uiState = MutableStateFlow(SleepUiState()) - val uiState: StateFlow = _uiState.asStateFlow() - - fun loadSleepData() { - viewModelScope.launch { - _uiState.value = _uiState.value.copy(isLoading = true) - - try { - val today = LocalDate.now() - val yesterday = today.minusDays(1) - - // Загружаем сон прошлой ночи - val lastNightSleep = repository.getSleepForDate(yesterday) - - // Загружаем последние записи сна - repository.getRecentSleepLogs().collect { logs -> - val weeklyAverage = calculateWeeklyAverage(logs) - val weeklyData = createWeeklyData(logs) - val insights = generateInsights(logs) - - _uiState.value = _uiState.value.copy( - lastNightSleep = lastNightSleep, - recentLogs = logs, - weeklyData = weeklyData, - weeklyAverage = weeklyAverage, - insights = insights, - isLoading = false - ) - } - - // Загружаем цель сна пользователя - repository.getUserProfile().collect { user -> - _uiState.value = _uiState.value.copy( - sleepGoal = user.dailySleepGoal - ) - } - - // Проверяем текущее качество сна - val todaySleep = repository.getSleepForDate(today) - _uiState.value = _uiState.value.copy( - todayQuality = todaySleep?.quality ?: "" - ) - - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = e.message - ) - } - } - } - - fun startSleepTracking() { - viewModelScope.launch { - try { - val now = LocalTime.now() - val bedTime = now.format(DateTimeFormatter.ofPattern("HH:mm")) - - val sleepLog = SleepLogEntity( - date = LocalDate.now(), - bedTime = bedTime, - wakeTime = "", - duration = 0f, - quality = "", - notes = "" - ) - - // TODO: Сохранить в базу данных и получить ID - _uiState.value = _uiState.value.copy( - isTracking = true, - currentSleep = sleepLog - ) - - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun stopSleepTracking() { - viewModelScope.launch { - try { - val currentSleep = _uiState.value.currentSleep - if (currentSleep != null) { - val now = LocalTime.now() - val wakeTime = now.format(DateTimeFormatter.ofPattern("HH:mm")) - - // Вычисляем продолжительность сна - val duration = calculateSleepDuration(currentSleep.bedTime, wakeTime) - - repository.addSleepRecord( - date = currentSleep.date, - bedTime = currentSleep.bedTime, - wakeTime = wakeTime, - quality = "Хорошее", // По умолчанию - notes = "" - ) - - _uiState.value = _uiState.value.copy( - isTracking = false, - currentSleep = null - ) - - loadSleepData() // Перезагружаем данные - } - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun updateSleepQuality(quality: String) { - viewModelScope.launch { - try { - val today = LocalDate.now() - val existingSleep = repository.getSleepForDate(today) - - if (existingSleep != null) { - // Обновляем существующую запись - repository.addSleepRecord( - date = today, - bedTime = existingSleep.bedTime, - wakeTime = existingSleep.wakeTime, - quality = quality, - notes = existingSleep.notes - ) - } else { - // Создаем новую запись только с качеством - repository.addSleepRecord( - date = today, - bedTime = "", - wakeTime = "", - quality = quality, - notes = "" - ) - } - - _uiState.value = _uiState.value.copy( - todayQuality = quality, - isEditMode = false - ) - - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun toggleEditMode() { - _uiState.value = _uiState.value.copy( - isEditMode = !_uiState.value.isEditMode - ) - } - - fun deleteSleepLog(sleepLog: SleepLogEntity) { - viewModelScope.launch { - try { - // TODO: Реализовать удаление записи через repository - loadSleepData() // Перезагружаем данные - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun updateSleepRecord(sleepLog: SleepLogEntity) { - viewModelScope.launch { - try { - repository.addSleepRecord( - date = sleepLog.date, - bedTime = sleepLog.bedTime, - wakeTime = sleepLog.wakeTime, - quality = sleepLog.quality, - notes = sleepLog.notes - ) - loadSleepData() // Перезагружаем данные - } catch (e: Exception) { - _uiState.value = _uiState.value.copy(error = e.message) - } - } - } - - fun updateNotes(notes: String) { - val currentSleep = _uiState.value.currentSleep - if (currentSleep != null) { - _uiState.value = _uiState.value.copy( - currentSleep = currentSleep.copy(notes = notes) - ) - } - } - - private fun calculateWeeklyAverage(logs: List): Float { - if (logs.isEmpty()) return 0f - val totalDuration = logs.sumOf { it.duration.toDouble() } - return (totalDuration / logs.size).toFloat() - } - - private fun createWeeklyData(logs: List): Map { - val weeklyData = mutableMapOf() - val today = LocalDate.now() - - for (i in 0..6) { - val date = today.minusDays(i.toLong()) - val sleepForDate = logs.find { it.date == date } - weeklyData[date] = sleepForDate?.duration ?: 0f - } - - return weeklyData - } - - private fun generateInsights(logs: List): List { - val insights = mutableListOf() - - if (logs.size >= 7) { - val averageDuration = calculateWeeklyAverage(logs) - val goal = _uiState.value.sleepGoal - - when { - averageDuration < goal - 1 -> { - insights.add("Вы спите в среднем на ${String.format("%.1f", goal - averageDuration)} часов меньше рекомендуемого") - } - averageDuration > goal + 1 -> { - insights.add("Вы спите больше рекомендуемого времени") - } - else -> { - insights.add("Ваш режим сна близок к оптимальному") - } - } - - // Анализ регулярности - val bedTimes = logs.mapNotNull { - if (it.bedTime.isNotEmpty()) { - val parts = it.bedTime.split(":") - if (parts.size == 2) { - parts[0].toIntOrNull()?.let { hour -> - hour * 60 + (parts[1].toIntOrNull() ?: 0) - } - } else null - } else null - } - - if (bedTimes.size >= 5) { - val avgBedTime = bedTimes.average() - val deviation = bedTimes.map { kotlin.math.abs(it - avgBedTime) }.average() - - if (deviation > 60) { // Больше часа отклонения - insights.add("Старайтесь ложиться спать в одно и то же время") - } else { - insights.add("У вас хороший регулярный режим сна") - } - } - - // Анализ качества - val qualityGood = logs.count { it.quality in listOf("Отличное", "Хорошее") } - val qualityPercent = (qualityGood.toFloat() / logs.size) * 100 - - when { - qualityPercent >= 80 -> insights.add("Качество вашего сна отличное!") - qualityPercent >= 60 -> insights.add("Качество сна можно улучшить") - else -> insights.add("Рекомендуем обратить внимание на гигиену сна") - } - } - - return insights - } - - private fun calculateSleepDuration(bedTime: String, wakeTime: String): Float { - try { - val bedParts = bedTime.split(":") - val wakeParts = wakeTime.split(":") - - if (bedParts.size == 2 && wakeParts.size == 2) { - val bedMinutes = bedParts[0].toInt() * 60 + bedParts[1].toInt() - val wakeMinutes = wakeParts[0].toInt() * 60 + wakeParts[1].toInt() - - val sleepMinutes = if (wakeMinutes > bedMinutes) { - wakeMinutes - bedMinutes - } else { - // Переход через полночь - (24 * 60 - bedMinutes) + wakeMinutes - } - - return sleepMinutes / 60f - } - } catch (e: Exception) { - // Если не удается рассчитать, возвращаем 8 часов по умолчанию - } - - return 8.0f - } - - fun clearError() { - _uiState.value = _uiState.value.copy(error = null) - } -} diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index db426ba..08a1e7b 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,8 +1,9 @@ - + 192.168.0.112 + 192.168.219.108 diff --git a/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/CycleAnalyticsTest.kt b/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/CycleAnalyticsTest.kt index e943da2..941349c 100644 --- a/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/CycleAnalyticsTest.kt +++ b/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/CycleAnalyticsTest.kt @@ -15,8 +15,7 @@ class CycleAnalyticsTest { endDate = LocalDate.now().minusDays(23), cycleLength = 28, flow = "medium", - symptoms = emptyList(), - mood = "neutral" + symptoms = emptyList() ), CyclePeriodEntity( id = 1, @@ -24,8 +23,7 @@ class CycleAnalyticsTest { endDate = LocalDate.now().minusDays(51), cycleLength = 28, flow = "medium", - symptoms = emptyList(), - mood = "neutral" + symptoms = emptyList() ) ) @@ -44,8 +42,7 @@ class CycleAnalyticsTest { endDate = LocalDate.now().minusDays(23), cycleLength = 28, flow = "medium", - symptoms = emptyList(), - mood = "neutral" + symptoms = emptyList() ), CyclePeriodEntity( id = 1, @@ -53,8 +50,7 @@ class CycleAnalyticsTest { endDate = LocalDate.now().minusDays(51), cycleLength = 28, flow = "medium", - symptoms = emptyList(), - mood = "neutral" + symptoms = emptyList() ) ) @@ -72,8 +68,7 @@ class CycleAnalyticsTest { endDate = LocalDate.now().minusDays(23), cycleLength = 28, flow = "medium", - symptoms = emptyList(), - mood = "neutral" + symptoms = emptyList() ) ) diff --git a/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalyticsTest.kt b/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalyticsTest.kt deleted file mode 100644 index 5d235db..0000000 --- a/app/src/test/java/kr/smartsoltech/wellshe/domain/analytics/SleepAnalyticsTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package kr.smartsoltech.wellshe.domain.analytics - -import kr.smartsoltech.wellshe.data.entity.SleepLogEntity -import org.junit.Assert.* -import org.junit.Test - -class SleepAnalyticsTest { - @Test - fun testSleepDebt() { - val logs = listOf( - SleepLogEntity( - id = 0, - date = java.time.LocalDate.now(), - bedTime = "22:00", - wakeTime = "06:00", - duration = 8.0f, - quality = "good", - notes = "" - ), - SleepLogEntity( - id = 0, - date = java.time.LocalDate.now().minusDays(1), - bedTime = "23:00", - wakeTime = "06:00", - duration = 7.0f, - quality = "normal", - notes = "" - ) - ) - val debt = SleepAnalytics.sleepDebt(logs, 8) - assertEquals(1, debt) - } -} diff --git a/gradle.properties b/gradle.properties index 20e2a01..72c7374 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true + +API_BASE_URL=http://192.168.219.108:8000/api/v1/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be95dd2..d659701 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ activityCompose = "1.8.2" composeBom = "2024.02.02" hilt = "2.48" composeCompiler = "1.5.14" +material = "1.13.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -28,6 +29,7 @@ androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-te androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }