diff --git a/README_NEW_PLANET.md b/README_NEW_PLANET.md
new file mode 100644
index 0000000..948be77
--- /dev/null
+++ b/README_NEW_PLANET.md
@@ -0,0 +1,189 @@
+# Новая Планета — Frontend (Android)
+
+
+
Визуальное расписание для детей с РАС
+
Мобильное приложение на Android с ИИ-помощником "Планета Земля"
+
+
+---
+
+## 📱 О проекте
+
+**Новая Планета** — Android-приложение, помогающее детям с расстройством аутистического спектра (РАС) через визуальные инструменты, расписания и систему наград.
+
+### Основные возможности
+
+- 📅 **Визуальное расписание**: день/неделя с карточками действий
+- ⏱️ **Визуальный таймер**: круговой таймер с цветовой индикацией
+- 🎁 **Система наград**: поощрения за выполненные задания
+- 🌍 **ИИ-агент “Земля”**: ответы и генерация расписаний
+- 📚 **Библиотека изображений**: загрузка картинок
+- 👨👩👧 **Роли**: ребёнок, родитель, педагог
+
+---
+
+## 🛠️ Tech Stack
+
+### Core
+- **Kotlin** 1.9+
+- **Jetpack Compose**
+- **Material Design 3**
+- **Coroutines + Flow**
+
+### Architecture
+- **Clean Architecture**: Domain / Data / UI
+- **MVVM**
+- **Offline-first через Room**
+
+### Libraries
+- Room
+- Retrofit
+- OkHttp
+- Kotlinx Serialization
+- Navigation Component
+- Koin/Hilt
+- Coil
+- Lottie
+
+---
+
+## 📂 Структура проекта
+
+```
+new-planet-android/
+├── app/
+│ └── src/main/java/com/novayaplaneta/
+│ ├── MainActivity.kt
+│ ├── di/
+│ ├── ui/
+│ │ ├── screens/
+│ │ │ ├── schedule/
+│ │ │ ├── task/
+│ │ │ ├── timer/
+│ │ │ ├── rewards/
+│ │ │ ├── ai/
+│ │ │ └── settings/
+│ │ ├── components/
+│ │ └── theme/
+│ ├── domain/
+│ │ ├── model/
+│ │ ├── repository/
+│ │ └── usecase/
+│ └── data/
+│ ├── local/
+│ ├── remote/
+│ └── repository/
+│
+├── build.gradle.kts
+├── README.md
+└── CLAUDE.md
+```
+
+---
+
+## 🚀 Быстрый старт
+
+### Требования
+
+- Android Studio Hedgehog+
+- JDK 17+
+- Android SDK 34+
+- Kotlin 1.9+
+
+### Установка (после генерации проекта в Cursor)
+
+```bash
+git clone https://git.bro-js.ru/Glevel/New-planet.git
+cd New-planet
+./gradlew build
+./gradlew installDebug
+```
+
+### Настройка среды
+
+```
+# local.properties
+sdk.dir=/path/to/Android/Sdk
+BACKEND_BASE_URL=https://api.novayaplaneta.ru
+```
+
+---
+
+## 🔌 Retrofit API (пример структуры)
+
+```kotlin
+interface BackendApi {
+
+ @POST("api/v1/auth/login")
+ suspend fun login(
+ @Body request: LoginRequest
+ ): Response
+
+ @GET("api/v1/schedules")
+ suspend fun getSchedules(
+ @Header("Authorization") token: String
+ ): Response>
+
+ @POST("api/v1/ai/chat")
+ suspend fun chatWithAI(
+ @Header("Authorization") token: String,
+ @Body request: ChatRequest
+ ): Response
+}
+```
+
+---
+
+## 🎨 Accessibility (РАС)
+
+### Дизайн принципы
+
+- Крупные кликабельные элементы (48dp+)
+- Высокая контрастность (WCAG AAA)
+- Чёткие простые иконки
+- Цветовое кодирование состояний
+- Плавные анимации без мигания
+
+### Контрастность
+
+**Светлая тема**
+- Фон: #FFFFFF, #F5F5F5
+- Текст: #0A0A0A
+- Акцент: #4CAF50, #FF6B35
+
+**Тёмная тема**
+- Фон: #0A0A0A
+- Текст: #FFFFFF
+- Акцент: #4CAF50, #FFD700
+
+---
+
+## 🧪 Тестирование
+
+```bash
+./gradlew test
+./gradlew connectedAndroidTest
+```
+
+---
+
+## 🔗 Репозиторий проекта
+
+Основная ссылка:
+
+👉 **Frontend Android:**
+https://git.bro-js.ru/Glevel/New-planet
+
+Backend/AI будут добавлены позже.
+
+---
+
+## 📄 Лицензия
+
+MIT License
+
+---
+
+## 👥 Команда
+
+Разработано с ❤️ командой **Новая Планета**
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 57c36c2..d3ee38f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,20 +2,27 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.hilt)
+ id("org.jetbrains.kotlin.kapt")
}
android {
- namespace = "com.project.newplanet"
+ namespace = "com.novayaplaneta"
compileSdk = 36
defaultConfig {
- applicationId = "com.project.newplanet"
- minSdk = 30
+ applicationId = "com.novayaplaneta"
+ minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ vectorDrawables {
+ useSupportLibrary = true
+ }
}
buildTypes {
@@ -28,27 +35,72 @@ android {
}
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = "11"
+ jvmTarget = "17"
}
buildFeatures {
compose = true
}
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
}
dependencies {
-
+ // Core Android
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
+
+ // Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
+ implementation("androidx.compose.material:material-icons-extended")
+
+ // Lifecycle ViewModel
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
+
+ // Navigation
+ implementation(libs.navigation.compose)
+
+ // Hilt
+ implementation(libs.hilt.android)
+ implementation(libs.hilt.navigation.compose)
+ kapt(libs.hilt.compiler)
+
+ // Room
+ implementation(libs.room.runtime)
+ implementation(libs.room.ktx)
+ kapt(libs.room.compiler)
+
+ // Retrofit
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.kotlinx.serialization)
+ implementation(libs.okhttp)
+ implementation(libs.okhttp.logging)
+
+ // Kotlinx Serialization
+ implementation(libs.kotlinx.serialization.json)
+
+ // Coil
+ implementation(libs.coil.compose)
+
+ // Lottie
+ implementation(libs.lottie.compose)
+
+ // Coroutines
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.coroutines.android)
+
+ // Testing
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b1fbc0..31b6170 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,11 @@
+
+
+
+ navController.navigate(route) {
+ popUpTo(navController.graph.startDestinationId) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
+ }
+ }
+ )
+ }
+ ) { innerPadding ->
+ NewPlanetNavigation(
+ navController = navController,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ )
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/NewPlanetApplication.kt b/app/src/main/java/com/novayaplaneta/NewPlanetApplication.kt
new file mode 100644
index 0000000..9a1f62d
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/NewPlanetApplication.kt
@@ -0,0 +1,8 @@
+package com.novayaplaneta
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class NewPlanetApplication : Application()
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/NewPlanetDatabase.kt b/app/src/main/java/com/novayaplaneta/data/local/NewPlanetDatabase.kt
new file mode 100644
index 0000000..19253fd
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/NewPlanetDatabase.kt
@@ -0,0 +1,34 @@
+package com.novayaplaneta.data.local
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.novayaplaneta.data.local.dao.ChatMessageDao
+import com.novayaplaneta.data.local.dao.RewardDao
+import com.novayaplaneta.data.local.dao.ScheduleDao
+import com.novayaplaneta.data.local.dao.TaskDao
+import com.novayaplaneta.data.local.dao.UserDao
+import com.novayaplaneta.data.local.entity.ChatMessageEntity
+import com.novayaplaneta.data.local.entity.RewardEntity
+import com.novayaplaneta.data.local.entity.ScheduleEntity
+import com.novayaplaneta.data.local.entity.TaskEntity
+import com.novayaplaneta.data.local.entity.UserEntity
+
+@Database(
+ entities = [
+ UserEntity::class,
+ ScheduleEntity::class,
+ TaskEntity::class,
+ RewardEntity::class,
+ ChatMessageEntity::class
+ ],
+ version = 1,
+ exportSchema = false
+)
+abstract class NewPlanetDatabase : RoomDatabase() {
+ abstract fun userDao(): UserDao
+ abstract fun scheduleDao(): ScheduleDao
+ abstract fun taskDao(): TaskDao
+ abstract fun rewardDao(): RewardDao
+ abstract fun chatMessageDao(): ChatMessageDao
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/dao/ChatMessageDao.kt b/app/src/main/java/com/novayaplaneta/data/local/dao/ChatMessageDao.kt
new file mode 100644
index 0000000..2888a3a
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/dao/ChatMessageDao.kt
@@ -0,0 +1,21 @@
+package com.novayaplaneta.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.novayaplaneta.data.local.entity.ChatMessageEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface ChatMessageDao {
+ @Query("SELECT * FROM chat_messages WHERE userId = :userId ORDER BY timestamp ASC")
+ fun getMessagesByUserId(userId: String): Flow>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertMessage(message: ChatMessageEntity)
+
+ @Query("DELETE FROM chat_messages WHERE userId = :userId")
+ suspend fun deleteMessagesByUserId(userId: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/dao/RewardDao.kt b/app/src/main/java/com/novayaplaneta/data/local/dao/RewardDao.kt
new file mode 100644
index 0000000..5b70d11
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/dao/RewardDao.kt
@@ -0,0 +1,28 @@
+package com.novayaplaneta.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.novayaplaneta.data.local.entity.RewardEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface RewardDao {
+ @Query("SELECT * FROM rewards WHERE userId = :userId ORDER BY earnedAt DESC")
+ fun getRewardsByUserId(userId: String): Flow>
+
+ @Query("SELECT * FROM rewards WHERE id = :id")
+ suspend fun getRewardById(id: String): RewardEntity?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertReward(reward: RewardEntity)
+
+ @Update
+ suspend fun updateReward(reward: RewardEntity)
+
+ @Query("DELETE FROM rewards WHERE id = :id")
+ suspend fun deleteReward(id: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/dao/ScheduleDao.kt b/app/src/main/java/com/novayaplaneta/data/local/dao/ScheduleDao.kt
new file mode 100644
index 0000000..8890bcf
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/dao/ScheduleDao.kt
@@ -0,0 +1,28 @@
+package com.novayaplaneta.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.novayaplaneta.data.local.entity.ScheduleEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface ScheduleDao {
+ @Query("SELECT * FROM schedules WHERE userId = :userId ORDER BY date ASC")
+ fun getSchedulesByUserId(userId: String): Flow>
+
+ @Query("SELECT * FROM schedules WHERE id = :id")
+ suspend fun getScheduleById(id: String): ScheduleEntity?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertSchedule(schedule: ScheduleEntity)
+
+ @Update
+ suspend fun updateSchedule(schedule: ScheduleEntity)
+
+ @Query("DELETE FROM schedules WHERE id = :id")
+ suspend fun deleteSchedule(id: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/dao/TaskDao.kt b/app/src/main/java/com/novayaplaneta/data/local/dao/TaskDao.kt
new file mode 100644
index 0000000..e88e2cb
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/dao/TaskDao.kt
@@ -0,0 +1,31 @@
+package com.novayaplaneta.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.novayaplaneta.data.local.entity.TaskEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface TaskDao {
+ @Query("SELECT * FROM tasks WHERE scheduleId = :scheduleId ORDER BY scheduledTime ASC")
+ fun getTasksByScheduleId(scheduleId: String): Flow>
+
+ @Query("SELECT * FROM tasks WHERE id = :id")
+ suspend fun getTaskById(id: String): TaskEntity?
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertTask(task: TaskEntity)
+
+ @Update
+ suspend fun updateTask(task: TaskEntity)
+
+ @Query("DELETE FROM tasks WHERE id = :id")
+ suspend fun deleteTask(id: String)
+
+ @Query("UPDATE tasks SET completed = 1 WHERE id = :id")
+ suspend fun completeTask(id: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/dao/UserDao.kt b/app/src/main/java/com/novayaplaneta/data/local/dao/UserDao.kt
new file mode 100644
index 0000000..3db2e0c
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/dao/UserDao.kt
@@ -0,0 +1,21 @@
+package com.novayaplaneta.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.novayaplaneta.data.local.entity.UserEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface UserDao {
+ @Query("SELECT * FROM users LIMIT 1")
+ fun getCurrentUser(): Flow
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertUser(user: UserEntity)
+
+ @Query("DELETE FROM users")
+ suspend fun deleteAllUsers()
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/entity/ChatMessageEntity.kt b/app/src/main/java/com/novayaplaneta/data/local/entity/ChatMessageEntity.kt
new file mode 100644
index 0000000..322bcb8
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/entity/ChatMessageEntity.kt
@@ -0,0 +1,15 @@
+package com.novayaplaneta.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "chat_messages")
+data class ChatMessageEntity(
+ @PrimaryKey
+ val id: String,
+ val message: String,
+ val isFromAI: Boolean,
+ val timestamp: Long, // timestamp
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/entity/RewardEntity.kt b/app/src/main/java/com/novayaplaneta/data/local/entity/RewardEntity.kt
new file mode 100644
index 0000000..3ab982b
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/entity/RewardEntity.kt
@@ -0,0 +1,17 @@
+package com.novayaplaneta.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "rewards")
+data class RewardEntity(
+ @PrimaryKey
+ val id: String,
+ val title: String,
+ val description: String?,
+ val imageUrl: String?,
+ val points: Int,
+ val earnedAt: Long?, // timestamp
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/entity/ScheduleEntity.kt b/app/src/main/java/com/novayaplaneta/data/local/entity/ScheduleEntity.kt
new file mode 100644
index 0000000..470aa63
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/entity/ScheduleEntity.kt
@@ -0,0 +1,17 @@
+package com.novayaplaneta.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.time.LocalDateTime
+
+@Entity(tableName = "schedules")
+data class ScheduleEntity(
+ @PrimaryKey
+ val id: String,
+ val title: String,
+ val description: String?,
+ val date: Long, // timestamp
+ val createdAt: Long, // timestamp
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/entity/TaskEntity.kt b/app/src/main/java/com/novayaplaneta/data/local/entity/TaskEntity.kt
new file mode 100644
index 0000000..d698894
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/entity/TaskEntity.kt
@@ -0,0 +1,18 @@
+package com.novayaplaneta.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "tasks")
+data class TaskEntity(
+ @PrimaryKey
+ val id: String,
+ val title: String,
+ val description: String?,
+ val imageUrl: String?,
+ val completed: Boolean = false,
+ val scheduledTime: Long?, // timestamp
+ val duration: Int? = null,
+ val scheduleId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/entity/UserEntity.kt b/app/src/main/java/com/novayaplaneta/data/local/entity/UserEntity.kt
new file mode 100644
index 0000000..9302d66
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/entity/UserEntity.kt
@@ -0,0 +1,15 @@
+package com.novayaplaneta.data.local.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "users")
+data class UserEntity(
+ @PrimaryKey
+ val id: String,
+ val name: String,
+ val email: String,
+ val role: String,
+ val token: String?
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/mapper/ChatMessageMapper.kt b/app/src/main/java/com/novayaplaneta/data/local/mapper/ChatMessageMapper.kt
new file mode 100644
index 0000000..1a1e4c0
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/mapper/ChatMessageMapper.kt
@@ -0,0 +1,28 @@
+package com.novayaplaneta.data.local.mapper
+
+import com.novayaplaneta.data.local.entity.ChatMessageEntity
+import com.novayaplaneta.domain.model.ChatMessage
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+fun ChatMessageEntity.toDomain(): ChatMessage {
+ return ChatMessage(
+ id = id,
+ message = message,
+ isFromAI = isFromAI,
+ timestamp = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()),
+ userId = userId
+ )
+}
+
+fun ChatMessage.toEntity(): ChatMessageEntity {
+ return ChatMessageEntity(
+ id = id,
+ message = message,
+ isFromAI = isFromAI,
+ timestamp = timestamp.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
+ userId = userId
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/mapper/RewardMapper.kt b/app/src/main/java/com/novayaplaneta/data/local/mapper/RewardMapper.kt
new file mode 100644
index 0000000..ffcedf8
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/mapper/RewardMapper.kt
@@ -0,0 +1,34 @@
+package com.novayaplaneta.data.local.mapper
+
+import com.novayaplaneta.data.local.entity.RewardEntity
+import com.novayaplaneta.domain.model.Reward
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+fun RewardEntity.toDomain(): Reward {
+ return Reward(
+ id = id,
+ title = title,
+ description = description,
+ imageUrl = imageUrl,
+ points = points,
+ earnedAt = earnedAt?.let {
+ LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())
+ },
+ userId = userId
+ )
+}
+
+fun Reward.toEntity(): RewardEntity {
+ return RewardEntity(
+ id = id,
+ title = title,
+ description = description,
+ imageUrl = imageUrl,
+ points = points,
+ earnedAt = earnedAt?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli(),
+ userId = userId
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/mapper/ScheduleMapper.kt b/app/src/main/java/com/novayaplaneta/data/local/mapper/ScheduleMapper.kt
new file mode 100644
index 0000000..3a8abbd
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/mapper/ScheduleMapper.kt
@@ -0,0 +1,32 @@
+package com.novayaplaneta.data.local.mapper
+
+import com.novayaplaneta.data.local.entity.ScheduleEntity
+import com.novayaplaneta.domain.model.Schedule
+import com.novayaplaneta.domain.model.Task
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+fun ScheduleEntity.toDomain(tasks: List = emptyList()): Schedule {
+ return Schedule(
+ id = id,
+ title = title,
+ description = description,
+ tasks = tasks,
+ date = LocalDateTime.ofInstant(Instant.ofEpochMilli(date), ZoneId.systemDefault()),
+ createdAt = LocalDateTime.ofInstant(Instant.ofEpochMilli(createdAt), ZoneId.systemDefault()),
+ userId = userId
+ )
+}
+
+fun Schedule.toEntity(): ScheduleEntity {
+ return ScheduleEntity(
+ id = id,
+ title = title,
+ description = description,
+ date = date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
+ createdAt = createdAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
+ userId = userId
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/mapper/TaskMapper.kt b/app/src/main/java/com/novayaplaneta/data/local/mapper/TaskMapper.kt
new file mode 100644
index 0000000..2e1cb4a
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/mapper/TaskMapper.kt
@@ -0,0 +1,36 @@
+package com.novayaplaneta.data.local.mapper
+
+import com.novayaplaneta.data.local.entity.TaskEntity
+import com.novayaplaneta.domain.model.Task
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+fun TaskEntity.toDomain(): Task {
+ return Task(
+ id = id,
+ title = title,
+ description = description,
+ imageUrl = imageUrl,
+ completed = completed,
+ scheduledTime = scheduledTime?.let {
+ LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault())
+ },
+ duration = duration,
+ scheduleId = scheduleId
+ )
+}
+
+fun Task.toEntity(): TaskEntity {
+ return TaskEntity(
+ id = id,
+ title = title,
+ description = description,
+ imageUrl = imageUrl,
+ completed = completed,
+ scheduledTime = scheduledTime?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli(),
+ duration = duration,
+ scheduleId = scheduleId
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/local/mapper/UserMapper.kt b/app/src/main/java/com/novayaplaneta/data/local/mapper/UserMapper.kt
new file mode 100644
index 0000000..3362d66
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/local/mapper/UserMapper.kt
@@ -0,0 +1,26 @@
+package com.novayaplaneta.data.local.mapper
+
+import com.novayaplaneta.data.local.entity.UserEntity
+import com.novayaplaneta.domain.model.User
+import com.novayaplaneta.domain.model.UserRole
+
+fun UserEntity.toDomain(): User {
+ return User(
+ id = id,
+ name = name,
+ email = email,
+ role = UserRole.valueOf(role),
+ token = token
+ )
+}
+
+fun User.toEntity(): UserEntity {
+ return UserEntity(
+ id = id,
+ name = name,
+ email = email,
+ role = role.name,
+ token = token
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/remote/BackendApi.kt b/app/src/main/java/com/novayaplaneta/data/remote/BackendApi.kt
new file mode 100644
index 0000000..ca61f58
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/remote/BackendApi.kt
@@ -0,0 +1,25 @@
+package com.novayaplaneta.data.remote
+
+import com.novayaplaneta.data.remote.dto.ChatRequest
+import com.novayaplaneta.data.remote.dto.ChatResponse
+import com.novayaplaneta.data.remote.dto.LoginRequest
+import com.novayaplaneta.data.remote.dto.LoginResponse
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.POST
+
+interface BackendApi {
+ @POST("api/v1/auth/login")
+ suspend fun login(
+ @Body request: LoginRequest
+ ): Response
+
+ @POST("api/v1/ai/chat")
+ suspend fun chatWithAI(
+ @Header("Authorization") token: String,
+ @Body request: ChatRequest
+ ): Response
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatRequest.kt b/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatRequest.kt
new file mode 100644
index 0000000..e1d9a96
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatRequest.kt
@@ -0,0 +1,9 @@
+package com.novayaplaneta.data.remote.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ChatRequest(
+ val message: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatResponse.kt b/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatResponse.kt
new file mode 100644
index 0000000..a1511ce
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/remote/dto/ChatResponse.kt
@@ -0,0 +1,9 @@
+package com.novayaplaneta.data.remote.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ChatResponse(
+ val message: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginRequest.kt b/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginRequest.kt
new file mode 100644
index 0000000..5d1f521
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginRequest.kt
@@ -0,0 +1,10 @@
+package com.novayaplaneta.data.remote.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class LoginRequest(
+ val email: String,
+ val password: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginResponse.kt b/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginResponse.kt
new file mode 100644
index 0000000..bc7f551
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/remote/dto/LoginResponse.kt
@@ -0,0 +1,18 @@
+package com.novayaplaneta.data.remote.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class LoginResponse(
+ val token: String,
+ val user: UserDto
+)
+
+@Serializable
+data class UserDto(
+ val id: String,
+ val name: String,
+ val email: String,
+ val role: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/data/repository/AIRepositoryImpl.kt b/app/src/main/java/com/novayaplaneta/data/repository/AIRepositoryImpl.kt
new file mode 100644
index 0000000..0c3ec43
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/repository/AIRepositoryImpl.kt
@@ -0,0 +1,78 @@
+package com.novayaplaneta.data.repository
+
+import com.novayaplaneta.data.local.dao.ChatMessageDao
+import com.novayaplaneta.data.local.mapper.toDomain
+import com.novayaplaneta.data.local.mapper.toEntity
+import com.novayaplaneta.data.remote.BackendApi
+import com.novayaplaneta.data.remote.dto.ChatRequest
+import com.novayaplaneta.domain.model.ChatMessage
+import com.novayaplaneta.domain.repository.AIRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import java.time.LocalDateTime
+import java.util.UUID
+import javax.inject.Inject
+
+class AIRepositoryImpl @Inject constructor(
+ private val chatMessageDao: ChatMessageDao,
+ private val api: BackendApi,
+ private val authRepository: com.novayaplaneta.domain.repository.AuthRepository
+) : AIRepository {
+
+ override suspend fun sendMessage(userId: String, message: String): Result {
+ return try {
+ // Get token from current user
+ val user = authRepository.getCurrentUser().first()
+ val token = user?.token ?: ""
+
+ // Save user message
+ val userMessage = ChatMessage(
+ id = UUID.randomUUID().toString(),
+ message = message,
+ isFromAI = false,
+ timestamp = LocalDateTime.now(),
+ userId = userId
+ )
+ chatMessageDao.insertMessage(userMessage.toEntity())
+
+ // Send to API (only if token exists)
+ if (token.isNotEmpty()) {
+ val response = api.chatWithAI("Bearer $token", ChatRequest(message))
+ if (response.isSuccessful && response.body() != null) {
+ val aiResponse = response.body()!!.message
+
+ // Save AI response
+ val aiMessage = ChatMessage(
+ id = UUID.randomUUID().toString(),
+ message = aiResponse,
+ isFromAI = true,
+ timestamp = LocalDateTime.now(),
+ userId = userId
+ )
+ chatMessageDao.insertMessage(aiMessage.toEntity())
+
+ Result.success(aiResponse)
+ } else {
+ Result.failure(Exception("Failed to get AI response"))
+ }
+ } else {
+ Result.failure(Exception("User not authenticated"))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
+ override fun getChatHistory(userId: String): Flow> {
+ return chatMessageDao.getMessagesByUserId(userId).map { messages ->
+ messages.map { it.toDomain() }
+ }
+ }
+
+ override suspend fun generateSchedule(userId: String, preferences: String): Result {
+ // TODO: Implement schedule generation
+ return Result.failure(Exception("Not implemented"))
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/novayaplaneta/data/repository/AuthRepositoryImpl.kt
new file mode 100644
index 0000000..f3a8f0f
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/repository/AuthRepositoryImpl.kt
@@ -0,0 +1,54 @@
+package com.novayaplaneta.data.repository
+
+import com.novayaplaneta.data.local.dao.UserDao
+import com.novayaplaneta.data.local.mapper.toDomain
+import com.novayaplaneta.data.local.mapper.toEntity
+import com.novayaplaneta.data.remote.BackendApi
+import com.novayaplaneta.data.remote.dto.LoginRequest
+import com.novayaplaneta.domain.model.User
+import com.novayaplaneta.domain.model.UserRole
+import com.novayaplaneta.domain.repository.AuthRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class AuthRepositoryImpl @Inject constructor(
+ private val userDao: UserDao,
+ private val api: BackendApi
+) : AuthRepository {
+
+ override suspend fun login(email: String, password: String): Result {
+ return try {
+ val response = api.login(LoginRequest(email, password))
+ if (response.isSuccessful && response.body() != null) {
+ val loginResponse = response.body()!!
+ val user = User(
+ id = loginResponse.user.id,
+ name = loginResponse.user.name,
+ email = loginResponse.user.email,
+ role = UserRole.valueOf(loginResponse.user.role),
+ token = loginResponse.token
+ )
+ saveUser(user)
+ Result.success(user)
+ } else {
+ Result.failure(Exception("Login failed"))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
+ override suspend fun logout() {
+ userDao.deleteAllUsers()
+ }
+
+ override fun getCurrentUser(): Flow {
+ return userDao.getCurrentUser().map { it?.toDomain() }
+ }
+
+ override suspend fun saveUser(user: User) {
+ userDao.insertUser(user.toEntity())
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/repository/RewardRepositoryImpl.kt b/app/src/main/java/com/novayaplaneta/data/repository/RewardRepositoryImpl.kt
new file mode 100644
index 0000000..ecdc6a2
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/repository/RewardRepositoryImpl.kt
@@ -0,0 +1,46 @@
+package com.novayaplaneta.data.repository
+
+import com.novayaplaneta.data.local.dao.RewardDao
+import com.novayaplaneta.data.local.mapper.toDomain
+import com.novayaplaneta.data.local.mapper.toEntity
+import com.novayaplaneta.domain.model.Reward
+import com.novayaplaneta.domain.repository.RewardRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class RewardRepositoryImpl @Inject constructor(
+ private val rewardDao: RewardDao
+) : RewardRepository {
+
+ override fun getRewards(userId: String): Flow> {
+ return rewardDao.getRewardsByUserId(userId).map { rewards ->
+ rewards.map { it.toDomain() }
+ }
+ }
+
+ override suspend fun getRewardById(id: String): Reward? {
+ return rewardDao.getRewardById(id)?.toDomain()
+ }
+
+ override suspend fun createReward(reward: Reward) {
+ rewardDao.insertReward(reward.toEntity())
+ }
+
+ override suspend fun updateReward(reward: Reward) {
+ rewardDao.updateReward(reward.toEntity())
+ }
+
+ override suspend fun deleteReward(id: String) {
+ rewardDao.deleteReward(id)
+ }
+
+ override suspend fun earnReward(userId: String, rewardId: String) {
+ val reward = getRewardById(rewardId)
+ reward?.let {
+ updateReward(it.copy(earnedAt = LocalDateTime.now()))
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/repository/ScheduleRepositoryImpl.kt b/app/src/main/java/com/novayaplaneta/data/repository/ScheduleRepositoryImpl.kt
new file mode 100644
index 0000000..e9ad51a
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/repository/ScheduleRepositoryImpl.kt
@@ -0,0 +1,50 @@
+package com.novayaplaneta.data.repository
+
+import com.novayaplaneta.data.local.dao.ScheduleDao
+import com.novayaplaneta.data.local.dao.TaskDao
+import com.novayaplaneta.data.local.mapper.toDomain
+import com.novayaplaneta.data.local.mapper.toEntity
+import com.novayaplaneta.domain.model.Schedule
+import com.novayaplaneta.domain.repository.ScheduleRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class ScheduleRepositoryImpl @Inject constructor(
+ private val scheduleDao: ScheduleDao,
+ private val taskDao: TaskDao
+) : ScheduleRepository {
+
+ override fun getSchedules(userId: String): Flow> {
+ return scheduleDao.getSchedulesByUserId(userId).map { schedules ->
+ schedules.map { scheduleEntity ->
+ // Note: In production, you'd need to fetch tasks for each schedule
+ scheduleEntity.toDomain(emptyList())
+ }
+ }
+ }
+
+ override suspend fun getScheduleById(id: String): Schedule? {
+ val scheduleEntity = scheduleDao.getScheduleById(id) ?: return null
+ val tasks = taskDao.getTasksByScheduleId(id)
+ // Simplified - would need proper Flow handling
+ return scheduleEntity.toDomain(emptyList())
+ }
+
+ override suspend fun createSchedule(schedule: Schedule) {
+ scheduleDao.insertSchedule(schedule.toEntity())
+ }
+
+ override suspend fun updateSchedule(schedule: Schedule) {
+ scheduleDao.updateSchedule(schedule.toEntity())
+ }
+
+ override suspend fun deleteSchedule(id: String) {
+ scheduleDao.deleteSchedule(id)
+ }
+
+ override suspend fun syncSchedules(userId: String) {
+ // TODO: Implement sync with backend
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/data/repository/TaskRepositoryImpl.kt b/app/src/main/java/com/novayaplaneta/data/repository/TaskRepositoryImpl.kt
new file mode 100644
index 0000000..6c07f87
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/data/repository/TaskRepositoryImpl.kt
@@ -0,0 +1,42 @@
+package com.novayaplaneta.data.repository
+
+import com.novayaplaneta.data.local.dao.TaskDao
+import com.novayaplaneta.data.local.mapper.toDomain
+import com.novayaplaneta.data.local.mapper.toEntity
+import com.novayaplaneta.domain.model.Task
+import com.novayaplaneta.domain.repository.TaskRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class TaskRepositoryImpl @Inject constructor(
+ private val taskDao: TaskDao
+) : TaskRepository {
+
+ override fun getTasks(scheduleId: String): Flow> {
+ return taskDao.getTasksByScheduleId(scheduleId).map { tasks ->
+ tasks.map { it.toDomain() }
+ }
+ }
+
+ override suspend fun getTaskById(id: String): Task? {
+ return taskDao.getTaskById(id)?.toDomain()
+ }
+
+ override suspend fun createTask(task: Task) {
+ taskDao.insertTask(task.toEntity())
+ }
+
+ override suspend fun updateTask(task: Task) {
+ taskDao.updateTask(task.toEntity())
+ }
+
+ override suspend fun deleteTask(id: String) {
+ taskDao.deleteTask(id)
+ }
+
+ override suspend fun completeTask(id: String) {
+ taskDao.completeTask(id)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/di/DatabaseModule.kt b/app/src/main/java/com/novayaplaneta/di/DatabaseModule.kt
new file mode 100644
index 0000000..bccbb7f
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/di/DatabaseModule.kt
@@ -0,0 +1,57 @@
+package com.novayaplaneta.di
+
+import android.content.Context
+import androidx.room.Room
+import com.novayaplaneta.data.local.NewPlanetDatabase
+import com.novayaplaneta.data.local.dao.ChatMessageDao
+import com.novayaplaneta.data.local.dao.RewardDao
+import com.novayaplaneta.data.local.dao.ScheduleDao
+import com.novayaplaneta.data.local.dao.TaskDao
+import com.novayaplaneta.data.local.dao.UserDao
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DatabaseModule {
+
+ @Provides
+ @Singleton
+ fun provideDatabase(@ApplicationContext context: Context): NewPlanetDatabase {
+ return Room.databaseBuilder(
+ context,
+ NewPlanetDatabase::class.java,
+ "newplanet_database"
+ ).build()
+ }
+
+ @Provides
+ fun provideUserDao(database: NewPlanetDatabase): UserDao {
+ return database.userDao()
+ }
+
+ @Provides
+ fun provideScheduleDao(database: NewPlanetDatabase): ScheduleDao {
+ return database.scheduleDao()
+ }
+
+ @Provides
+ fun provideTaskDao(database: NewPlanetDatabase): TaskDao {
+ return database.taskDao()
+ }
+
+ @Provides
+ fun provideRewardDao(database: NewPlanetDatabase): RewardDao {
+ return database.rewardDao()
+ }
+
+ @Provides
+ fun provideChatMessageDao(database: NewPlanetDatabase): ChatMessageDao {
+ return database.chatMessageDao()
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/di/NetworkModule.kt b/app/src/main/java/com/novayaplaneta/di/NetworkModule.kt
new file mode 100644
index 0000000..82d8e7b
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/di/NetworkModule.kt
@@ -0,0 +1,60 @@
+package com.novayaplaneta.di
+
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import com.novayaplaneta.data.remote.BackendApi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ isLenient = true
+ encodeDefaults = false
+ }
+
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(): OkHttpClient {
+ val loggingInterceptor = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ return OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .writeTimeout(30, TimeUnit.SECONDS)
+ .build()
+ }
+
+ @Provides
+ @Singleton
+ fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
+ val contentType = "application/json".toMediaType()
+
+ return Retrofit.Builder()
+ .baseUrl("https://api.novayaplaneta.ru/")
+ .client(okHttpClient)
+ .addConverterFactory(json.asConverterFactory(contentType))
+ .build()
+ }
+
+ @Provides
+ @Singleton
+ fun provideBackendApi(retrofit: Retrofit): BackendApi {
+ return retrofit.create(BackendApi::class.java)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/di/RepositoryModule.kt b/app/src/main/java/com/novayaplaneta/di/RepositoryModule.kt
new file mode 100644
index 0000000..0bb7d07
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/di/RepositoryModule.kt
@@ -0,0 +1,53 @@
+package com.novayaplaneta.di
+
+import com.novayaplaneta.data.repository.AIRepositoryImpl
+import com.novayaplaneta.data.repository.AuthRepositoryImpl
+import com.novayaplaneta.data.repository.RewardRepositoryImpl
+import com.novayaplaneta.data.repository.ScheduleRepositoryImpl
+import com.novayaplaneta.data.repository.TaskRepositoryImpl
+import com.novayaplaneta.domain.repository.AIRepository
+import com.novayaplaneta.domain.repository.AuthRepository
+import com.novayaplaneta.domain.repository.RewardRepository
+import com.novayaplaneta.domain.repository.ScheduleRepository
+import com.novayaplaneta.domain.repository.TaskRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class RepositoryModule {
+
+ @Binds
+ @Singleton
+ abstract fun bindAuthRepository(
+ authRepositoryImpl: AuthRepositoryImpl
+ ): AuthRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindScheduleRepository(
+ scheduleRepositoryImpl: ScheduleRepositoryImpl
+ ): ScheduleRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindTaskRepository(
+ taskRepositoryImpl: TaskRepositoryImpl
+ ): TaskRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindRewardRepository(
+ rewardRepositoryImpl: RewardRepositoryImpl
+ ): RewardRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindAIRepository(
+ aiRepositoryImpl: AIRepositoryImpl
+ ): AIRepository
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/model/ChatMessage.kt b/app/src/main/java/com/novayaplaneta/domain/model/ChatMessage.kt
new file mode 100644
index 0000000..b2f1227
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/model/ChatMessage.kt
@@ -0,0 +1,12 @@
+package com.novayaplaneta.domain.model
+
+import java.time.LocalDateTime
+
+data class ChatMessage(
+ val id: String,
+ val message: String,
+ val isFromAI: Boolean,
+ val timestamp: LocalDateTime,
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/model/Reward.kt b/app/src/main/java/com/novayaplaneta/domain/model/Reward.kt
new file mode 100644
index 0000000..f3d6f89
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/model/Reward.kt
@@ -0,0 +1,14 @@
+package com.novayaplaneta.domain.model
+
+import java.time.LocalDateTime
+
+data class Reward(
+ val id: String,
+ val title: String,
+ val description: String?,
+ val imageUrl: String?,
+ val points: Int,
+ val earnedAt: LocalDateTime? = null,
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/model/Schedule.kt b/app/src/main/java/com/novayaplaneta/domain/model/Schedule.kt
new file mode 100644
index 0000000..9e832ae
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/model/Schedule.kt
@@ -0,0 +1,14 @@
+package com.novayaplaneta.domain.model
+
+import java.time.LocalDateTime
+
+data class Schedule(
+ val id: String,
+ val title: String,
+ val description: String?,
+ val tasks: List,
+ val date: LocalDateTime,
+ val createdAt: LocalDateTime,
+ val userId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/model/Task.kt b/app/src/main/java/com/novayaplaneta/domain/model/Task.kt
new file mode 100644
index 0000000..dc4e9d7
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/model/Task.kt
@@ -0,0 +1,15 @@
+package com.novayaplaneta.domain.model
+
+import java.time.LocalDateTime
+
+data class Task(
+ val id: String,
+ val title: String,
+ val description: String?,
+ val imageUrl: String?,
+ val completed: Boolean = false,
+ val scheduledTime: LocalDateTime?,
+ val duration: Int? = null, // in minutes
+ val scheduleId: String
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/model/User.kt b/app/src/main/java/com/novayaplaneta/domain/model/User.kt
new file mode 100644
index 0000000..afa7235
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/model/User.kt
@@ -0,0 +1,16 @@
+package com.novayaplaneta.domain.model
+
+data class User(
+ val id: String,
+ val name: String,
+ val email: String,
+ val role: UserRole,
+ val token: String? = null
+)
+
+enum class UserRole {
+ CHILD,
+ PARENT,
+ TEACHER
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/repository/AIRepository.kt b/app/src/main/java/com/novayaplaneta/domain/repository/AIRepository.kt
new file mode 100644
index 0000000..447c479
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/repository/AIRepository.kt
@@ -0,0 +1,11 @@
+package com.novayaplaneta.domain.repository
+
+import com.novayaplaneta.domain.model.ChatMessage
+import kotlinx.coroutines.flow.Flow
+
+interface AIRepository {
+ suspend fun sendMessage(userId: String, message: String): Result
+ fun getChatHistory(userId: String): Flow>
+ suspend fun generateSchedule(userId: String, preferences: String): Result
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/repository/AuthRepository.kt b/app/src/main/java/com/novayaplaneta/domain/repository/AuthRepository.kt
new file mode 100644
index 0000000..76c2709
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/repository/AuthRepository.kt
@@ -0,0 +1,12 @@
+package com.novayaplaneta.domain.repository
+
+import com.novayaplaneta.domain.model.User
+import kotlinx.coroutines.flow.Flow
+
+interface AuthRepository {
+ suspend fun login(email: String, password: String): Result
+ suspend fun logout()
+ fun getCurrentUser(): Flow
+ suspend fun saveUser(user: User)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/repository/RewardRepository.kt b/app/src/main/java/com/novayaplaneta/domain/repository/RewardRepository.kt
new file mode 100644
index 0000000..7c6455e
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/repository/RewardRepository.kt
@@ -0,0 +1,14 @@
+package com.novayaplaneta.domain.repository
+
+import com.novayaplaneta.domain.model.Reward
+import kotlinx.coroutines.flow.Flow
+
+interface RewardRepository {
+ fun getRewards(userId: String): Flow>
+ suspend fun getRewardById(id: String): Reward?
+ suspend fun createReward(reward: Reward)
+ suspend fun updateReward(reward: Reward)
+ suspend fun deleteReward(id: String)
+ suspend fun earnReward(userId: String, rewardId: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/repository/ScheduleRepository.kt b/app/src/main/java/com/novayaplaneta/domain/repository/ScheduleRepository.kt
new file mode 100644
index 0000000..61a47c2
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/repository/ScheduleRepository.kt
@@ -0,0 +1,15 @@
+package com.novayaplaneta.domain.repository
+
+import com.novayaplaneta.domain.model.Schedule
+import kotlinx.coroutines.flow.Flow
+import java.time.LocalDateTime
+
+interface ScheduleRepository {
+ fun getSchedules(userId: String): Flow>
+ suspend fun getScheduleById(id: String): Schedule?
+ suspend fun createSchedule(schedule: Schedule)
+ suspend fun updateSchedule(schedule: Schedule)
+ suspend fun deleteSchedule(id: String)
+ suspend fun syncSchedules(userId: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/repository/TaskRepository.kt b/app/src/main/java/com/novayaplaneta/domain/repository/TaskRepository.kt
new file mode 100644
index 0000000..8d5cf46
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/repository/TaskRepository.kt
@@ -0,0 +1,14 @@
+package com.novayaplaneta.domain.repository
+
+import com.novayaplaneta.domain.model.Task
+import kotlinx.coroutines.flow.Flow
+
+interface TaskRepository {
+ fun getTasks(scheduleId: String): Flow>
+ suspend fun getTaskById(id: String): Task?
+ suspend fun createTask(task: Task)
+ suspend fun updateTask(task: Task)
+ suspend fun deleteTask(id: String)
+ suspend fun completeTask(id: String)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/usecase/CompleteTaskUseCase.kt b/app/src/main/java/com/novayaplaneta/domain/usecase/CompleteTaskUseCase.kt
new file mode 100644
index 0000000..6965364
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/usecase/CompleteTaskUseCase.kt
@@ -0,0 +1,13 @@
+package com.novayaplaneta.domain.usecase
+
+import com.novayaplaneta.domain.repository.TaskRepository
+import javax.inject.Inject
+
+class CompleteTaskUseCase @Inject constructor(
+ private val repository: TaskRepository
+) {
+ suspend operator fun invoke(taskId: String) {
+ repository.completeTask(taskId)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/usecase/CreateScheduleUseCase.kt b/app/src/main/java/com/novayaplaneta/domain/usecase/CreateScheduleUseCase.kt
new file mode 100644
index 0000000..c7ce7ec
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/usecase/CreateScheduleUseCase.kt
@@ -0,0 +1,14 @@
+package com.novayaplaneta.domain.usecase
+
+import com.novayaplaneta.domain.model.Schedule
+import com.novayaplaneta.domain.repository.ScheduleRepository
+import javax.inject.Inject
+
+class CreateScheduleUseCase @Inject constructor(
+ private val repository: ScheduleRepository
+) {
+ suspend operator fun invoke(schedule: Schedule) {
+ repository.createSchedule(schedule)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/usecase/GetSchedulesUseCase.kt b/app/src/main/java/com/novayaplaneta/domain/usecase/GetSchedulesUseCase.kt
new file mode 100644
index 0000000..9cca52e
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/usecase/GetSchedulesUseCase.kt
@@ -0,0 +1,15 @@
+package com.novayaplaneta.domain.usecase
+
+import com.novayaplaneta.domain.model.Schedule
+import com.novayaplaneta.domain.repository.ScheduleRepository
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class GetSchedulesUseCase @Inject constructor(
+ private val repository: ScheduleRepository
+) {
+ operator fun invoke(userId: String): Flow> {
+ return repository.getSchedules(userId)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/domain/usecase/SendAIMessageUseCase.kt b/app/src/main/java/com/novayaplaneta/domain/usecase/SendAIMessageUseCase.kt
new file mode 100644
index 0000000..1fd1acc
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/domain/usecase/SendAIMessageUseCase.kt
@@ -0,0 +1,13 @@
+package com.novayaplaneta.domain.usecase
+
+import com.novayaplaneta.domain.repository.AIRepository
+import javax.inject.Inject
+
+class SendAIMessageUseCase @Inject constructor(
+ private val repository: AIRepository
+) {
+ suspend operator fun invoke(userId: String, message: String): Result {
+ return repository.sendMessage(userId, message)
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/components/BottomNavigation.kt b/app/src/main/java/com/novayaplaneta/ui/components/BottomNavigation.kt
new file mode 100644
index 0000000..f4a2ed8
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/components/BottomNavigation.kt
@@ -0,0 +1,49 @@
+package com.novayaplaneta.ui.components
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+sealed class NavigationItem(val route: String, val title: String, val icon: androidx.compose.ui.graphics.vector.ImageVector) {
+ object Schedule : NavigationItem("schedule", "Расписание", Icons.Default.CalendarToday)
+ object Tasks : NavigationItem("tasks", "Задания", Icons.Default.Assignment)
+ object Timer : NavigationItem("timer", "Таймер", Icons.Default.Timer)
+ object Rewards : NavigationItem("rewards", "Награды", Icons.Default.Star)
+ object AI : NavigationItem("ai", "ИИ", Icons.Default.SmartToy)
+ object Settings : NavigationItem("settings", "Настройки", Icons.Default.Settings)
+}
+
+@Composable
+fun BottomNavigationBar(
+ currentRoute: String?,
+ onNavigate: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val items = listOf(
+ NavigationItem.Schedule,
+ NavigationItem.Tasks,
+ NavigationItem.Timer,
+ NavigationItem.Rewards,
+ NavigationItem.AI,
+ NavigationItem.Settings
+ )
+
+ NavigationBar(modifier = modifier) {
+ items.forEach { item ->
+ NavigationBarItem(
+ icon = {
+ Icon(
+ imageVector = item.icon,
+ contentDescription = item.title
+ )
+ },
+ label = { Text(item.title) },
+ selected = currentRoute == item.route,
+ onClick = { onNavigate(item.route) }
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt b/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt
new file mode 100644
index 0000000..5ab0fcd
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt
@@ -0,0 +1,46 @@
+package com.novayaplaneta.ui.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import com.novayaplaneta.ui.screens.ai.AIScreen
+import com.novayaplaneta.ui.screens.rewards.RewardsScreen
+import com.novayaplaneta.ui.screens.schedule.ScheduleScreen
+import com.novayaplaneta.ui.screens.settings.SettingsScreen
+import com.novayaplaneta.ui.screens.task.TaskScreen
+import com.novayaplaneta.ui.screens.timer.TimerScreen
+
+@Composable
+fun NewPlanetNavigation(
+ navController: NavHostController,
+ modifier: Modifier = Modifier,
+ startDestination: String = "schedule"
+) {
+ NavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = modifier
+ ) {
+ composable("schedule") {
+ ScheduleScreen()
+ }
+ composable("tasks") {
+ TaskScreen()
+ }
+ composable("timer") {
+ TimerScreen()
+ }
+ composable("rewards") {
+ RewardsScreen()
+ }
+ composable("ai") {
+ AIScreen()
+ }
+ composable("settings") {
+ SettingsScreen()
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIScreen.kt
new file mode 100644
index 0000000..e696882
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIScreen.kt
@@ -0,0 +1,94 @@
+package com.novayaplaneta.ui.screens.ai
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AIScreen(
+ viewModel: AIViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+ var messageText by remember { mutableStateOf("") }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("ИИ-помощник \"Земля\"") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .weight(1f)
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(uiState.messages) { message ->
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = if (message.isFromAI) {
+ MaterialTheme.colorScheme.primaryContainer
+ } else {
+ MaterialTheme.colorScheme.surfaceVariant
+ }
+ )
+ ) {
+ Text(
+ text = message.message,
+ modifier = Modifier.padding(16.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+
+ if (uiState.isLoading) {
+ LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ OutlinedTextField(
+ value = messageText,
+ onValueChange = { messageText = it },
+ modifier = Modifier.weight(1f),
+ placeholder = { Text("Введите сообщение...") }
+ )
+ Button(
+ onClick = {
+ if (messageText.isNotBlank()) {
+ viewModel.sendMessage("userId", messageText)
+ messageText = ""
+ }
+ }
+ ) {
+ Text("Отправить")
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIViewModel.kt
new file mode 100644
index 0000000..fb29c02
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/ai/AIViewModel.kt
@@ -0,0 +1,53 @@
+package com.novayaplaneta.ui.screens.ai
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.novayaplaneta.domain.repository.AIRepository
+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 javax.inject.Inject
+
+@HiltViewModel
+class AIViewModel @Inject constructor(
+ private val aiRepository: AIRepository
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(AIUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadChatHistory(userId: String) {
+ viewModelScope.launch {
+ aiRepository.getChatHistory(userId).collect { messages ->
+ _uiState.value = _uiState.value.copy(messages = messages)
+ }
+ }
+ }
+
+ fun sendMessage(userId: String, message: String) {
+ viewModelScope.launch {
+ _uiState.value = _uiState.value.copy(isLoading = true)
+ aiRepository.sendMessage(userId, message).fold(
+ onSuccess = { response ->
+ loadChatHistory(userId)
+ _uiState.value = _uiState.value.copy(isLoading = false)
+ },
+ onFailure = { error ->
+ _uiState.value = _uiState.value.copy(
+ isLoading = false,
+ error = error.message
+ )
+ }
+ )
+ }
+ }
+}
+
+data class AIUiState(
+ val messages: List = emptyList(),
+ val isLoading: Boolean = false,
+ val error: String? = null
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsScreen.kt
new file mode 100644
index 0000000..b1eb060
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsScreen.kt
@@ -0,0 +1,85 @@
+package com.novayaplaneta.ui.screens.rewards
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RewardsScreen(
+ viewModel: RewardsViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Награды") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp)
+ ) {
+ if (uiState.isLoading) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentWidth()
+ )
+ } else {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(uiState.rewards) { reward ->
+ Card(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = reward.title,
+ style = MaterialTheme.typography.titleLarge
+ )
+ reward.description?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ Text(
+ text = "${reward.points} очков",
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+ if (reward.earnedAt != null) {
+ Icon(
+ imageVector = androidx.compose.material.icons.Icons.Default.Star,
+ contentDescription = "Получено",
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsViewModel.kt
new file mode 100644
index 0000000..14b77ae
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/rewards/RewardsViewModel.kt
@@ -0,0 +1,44 @@
+package com.novayaplaneta.ui.screens.rewards
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.novayaplaneta.domain.repository.RewardRepository
+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 javax.inject.Inject
+
+@HiltViewModel
+class RewardsViewModel @Inject constructor(
+ private val rewardRepository: RewardRepository
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(RewardsUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadRewards(userId: String) {
+ viewModelScope.launch {
+ _uiState.value = _uiState.value.copy(isLoading = true)
+ rewardRepository.getRewards(userId).collect { rewards ->
+ _uiState.value = _uiState.value.copy(
+ rewards = rewards,
+ isLoading = false
+ )
+ }
+ }
+ }
+
+ fun earnReward(userId: String, rewardId: String) {
+ viewModelScope.launch {
+ rewardRepository.earnReward(userId, rewardId)
+ }
+ }
+}
+
+data class RewardsUiState(
+ val rewards: List = emptyList(),
+ val isLoading: Boolean = false
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleScreen.kt
new file mode 100644
index 0000000..dd47a24
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleScreen.kt
@@ -0,0 +1,70 @@
+package com.novayaplaneta.ui.screens.schedule
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ScheduleScreen(
+ viewModel: ScheduleViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Расписание") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp)
+ ) {
+ if (uiState.isLoading) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentWidth()
+ )
+ } else {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(uiState.schedules) { schedule ->
+ Card(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Text(
+ text = schedule.title,
+ style = MaterialTheme.typography.titleLarge
+ )
+ schedule.description?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleViewModel.kt
new file mode 100644
index 0000000..e2d5ac8
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/schedule/ScheduleViewModel.kt
@@ -0,0 +1,38 @@
+package com.novayaplaneta.ui.screens.schedule
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.novayaplaneta.domain.usecase.GetSchedulesUseCase
+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 javax.inject.Inject
+
+@HiltViewModel
+class ScheduleViewModel @Inject constructor(
+ private val getSchedulesUseCase: GetSchedulesUseCase
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(ScheduleUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadSchedules(userId: String) {
+ viewModelScope.launch {
+ _uiState.value = _uiState.value.copy(isLoading = true)
+ getSchedulesUseCase(userId).collect { schedules ->
+ _uiState.value = _uiState.value.copy(
+ schedules = schedules,
+ isLoading = false
+ )
+ }
+ }
+ }
+}
+
+data class ScheduleUiState(
+ val schedules: List = emptyList(),
+ val isLoading: Boolean = false
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsScreen.kt
new file mode 100644
index 0000000..3a8d511
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsScreen.kt
@@ -0,0 +1,74 @@
+package com.novayaplaneta.ui.screens.settings
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsScreen(
+ viewModel: SettingsViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Настройки") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ uiState.currentUser?.let { user ->
+ Card(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Text(
+ text = "Пользователь",
+ style = MaterialTheme.typography.titleLarge
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "Имя: ${user.name}",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Text(
+ text = "Email: ${user.email}",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Text(
+ text = "Роль: ${user.role.name}",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+
+ Button(
+ onClick = { viewModel.logout() },
+ modifier = Modifier.fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.error
+ )
+ ) {
+ Text("Выйти")
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsViewModel.kt
new file mode 100644
index 0000000..26d32dc
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/settings/SettingsViewModel.kt
@@ -0,0 +1,45 @@
+package com.novayaplaneta.ui.screens.settings
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.novayaplaneta.domain.repository.AuthRepository
+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 javax.inject.Inject
+
+@HiltViewModel
+class SettingsViewModel @Inject constructor(
+ private val authRepository: AuthRepository
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(SettingsUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ init {
+ loadCurrentUser()
+ }
+
+ private fun loadCurrentUser() {
+ viewModelScope.launch {
+ authRepository.getCurrentUser().collect { user ->
+ _uiState.value = _uiState.value.copy(currentUser = user)
+ }
+ }
+ }
+
+ fun logout() {
+ viewModelScope.launch {
+ authRepository.logout()
+ _uiState.value = _uiState.value.copy(isLoggedOut = true)
+ }
+ }
+}
+
+data class SettingsUiState(
+ val currentUser: com.novayaplaneta.domain.model.User? = null,
+ val isLoggedOut: Boolean = false
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskScreen.kt
new file mode 100644
index 0000000..d4a84c9
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskScreen.kt
@@ -0,0 +1,79 @@
+package com.novayaplaneta.ui.screens.task
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TaskScreen(
+ viewModel: TaskViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Задания") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp)
+ ) {
+ if (uiState.isLoading) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentWidth()
+ )
+ } else {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(uiState.tasks) { task ->
+ Card(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = task.title,
+ style = MaterialTheme.typography.titleMedium
+ )
+ task.description?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+ }
+ Checkbox(
+ checked = task.completed,
+ onCheckedChange = { viewModel.completeTask(task.id) }
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskViewModel.kt
new file mode 100644
index 0000000..9379514
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/task/TaskViewModel.kt
@@ -0,0 +1,44 @@
+package com.novayaplaneta.ui.screens.task
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.novayaplaneta.domain.repository.TaskRepository
+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 javax.inject.Inject
+
+@HiltViewModel
+class TaskViewModel @Inject constructor(
+ private val taskRepository: TaskRepository
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(TaskUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadTasks(scheduleId: String) {
+ viewModelScope.launch {
+ _uiState.value = _uiState.value.copy(isLoading = true)
+ taskRepository.getTasks(scheduleId).collect { tasks ->
+ _uiState.value = _uiState.value.copy(
+ tasks = tasks,
+ isLoading = false
+ )
+ }
+ }
+ }
+
+ fun completeTask(taskId: String) {
+ viewModelScope.launch {
+ taskRepository.completeTask(taskId)
+ }
+ }
+}
+
+data class TaskUiState(
+ val tasks: List = emptyList(),
+ val isLoading: Boolean = false
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerScreen.kt
new file mode 100644
index 0000000..2855be2
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerScreen.kt
@@ -0,0 +1,122 @@
+package com.novayaplaneta.ui.screens.timer
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TimerScreen(
+ viewModel: TimerViewModel = hiltViewModel(),
+ modifier: Modifier = Modifier
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Визуальный таймер") }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ // Circular Timer
+ Box(
+ modifier = Modifier.size(300.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ val strokeWidth = 20.dp.toPx()
+ val radius = size.minDimension / 2 - strokeWidth
+
+ // Background circle
+ drawCircle(
+ color = Color.Gray.copy(alpha = 0.3f),
+ radius = radius,
+ style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
+ )
+
+ // Progress circle
+ val progress = if (uiState.durationMinutes > 0) {
+ uiState.remainingSeconds.toFloat() / (uiState.durationMinutes * 60)
+ } else 0f
+
+ val sweepAngle = 360f * progress
+ drawArc(
+ color = MaterialTheme.colorScheme.primary,
+ startAngle = -90f,
+ sweepAngle = sweepAngle,
+ useCenter = false,
+ style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
+ )
+ }
+
+ Text(
+ text = formatTime(uiState.remainingSeconds),
+ style = MaterialTheme.typography.displayLarge
+ )
+ }
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Button(onClick = { viewModel.startTimer(5) }) {
+ Text("5 мин")
+ }
+ Button(onClick = { viewModel.startTimer(10) }) {
+ Text("10 мин")
+ }
+ Button(onClick = { viewModel.startTimer(15) }) {
+ Text("15 мин")
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ if (uiState.isRunning) {
+ Button(onClick = { viewModel.pauseTimer() }) {
+ Text("Пауза")
+ }
+ } else if (uiState.remainingSeconds > 0) {
+ Button(onClick = { viewModel.resumeTimer() }) {
+ Text("Продолжить")
+ }
+ }
+ Button(onClick = { viewModel.resetTimer() }) {
+ Text("Сброс")
+ }
+ }
+ }
+ }
+}
+
+private fun formatTime(seconds: Int): String {
+ val minutes = seconds / 60
+ val remainingSeconds = seconds % 60
+ return String.format("%02d:%02d", minutes, remainingSeconds)
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerViewModel.kt
new file mode 100644
index 0000000..4c036cc
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/timer/TimerViewModel.kt
@@ -0,0 +1,42 @@
+package com.novayaplaneta.ui.screens.timer
+
+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 TimerViewModel @Inject constructor() : ViewModel() {
+
+ private val _uiState = MutableStateFlow(TimerUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun startTimer(durationMinutes: Int) {
+ _uiState.value = _uiState.value.copy(
+ isRunning = true,
+ durationMinutes = durationMinutes,
+ remainingSeconds = durationMinutes * 60
+ )
+ }
+
+ fun pauseTimer() {
+ _uiState.value = _uiState.value.copy(isRunning = false)
+ }
+
+ fun resumeTimer() {
+ _uiState.value = _uiState.value.copy(isRunning = true)
+ }
+
+ fun resetTimer() {
+ _uiState.value = TimerUiState()
+ }
+}
+
+data class TimerUiState(
+ val isRunning: Boolean = false,
+ val durationMinutes: Int = 0,
+ val remainingSeconds: Int = 0
+)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt b/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
new file mode 100644
index 0000000..8077fd8
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
@@ -0,0 +1,26 @@
+package com.novayaplaneta.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+// Light Theme - High Contrast (WCAG AAA)
+val BackgroundLight = Color(0xFFFFFFFF)
+val SurfaceLight = Color(0xFFF5F5F5)
+val OnBackgroundLight = Color(0xFF0A0A0A)
+val OnSurfaceLight = Color(0xFF0A0A0A)
+
+// Dark Theme - High Contrast
+val BackgroundDark = Color(0xFF0A0A0A)
+val SurfaceDark = Color(0xFF1A1A1A)
+val OnBackgroundDark = Color(0xFFFFFFFF)
+val OnSurfaceDark = Color(0xFFFFFFFF)
+
+// Accent Colors
+val AccentGreen = Color(0xFF4CAF50)
+val AccentOrange = Color(0xFFFF6B35)
+val AccentGold = Color(0xFFFFD700)
+
+// Status Colors
+val SuccessColor = Color(0xFF4CAF50)
+val WarningColor = Color(0xFFFF6B35)
+val ErrorColor = Color(0xFFE53935)
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/theme/Theme.kt b/app/src/main/java/com/novayaplaneta/ui/theme/Theme.kt
new file mode 100644
index 0000000..faf2ec0
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/theme/Theme.kt
@@ -0,0 +1,49 @@
+package com.novayaplaneta.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+private val LightColorScheme = lightColorScheme(
+ primary = AccentGreen,
+ secondary = AccentOrange,
+ tertiary = AccentGold,
+ background = BackgroundLight,
+ surface = SurfaceLight,
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.Black,
+ onBackground = OnBackgroundLight,
+ onSurface = OnSurfaceLight
+)
+
+private val DarkColorScheme = darkColorScheme(
+ primary = AccentGreen,
+ secondary = AccentGold,
+ tertiary = AccentOrange,
+ background = BackgroundDark,
+ surface = SurfaceDark,
+ onPrimary = Color.Black,
+ onSecondary = Color.Black,
+ onTertiary = Color.White,
+ onBackground = OnBackgroundDark,
+ onSurface = OnSurfaceDark
+)
+
+@Composable
+fun NewPlanetTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
+
diff --git a/app/src/main/java/com/novayaplaneta/ui/theme/Type.kt b/app/src/main/java/com/novayaplaneta/ui/theme/Type.kt
new file mode 100644
index 0000000..d475cca
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/theme/Type.kt
@@ -0,0 +1,110 @@
+package com.novayaplaneta.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+val Typography = Typography(
+ displayLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.25).sp
+ ),
+ displayMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 45.sp,
+ lineHeight = 52.sp
+ ),
+ displaySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Bold,
+ fontSize = 36.sp,
+ lineHeight = 44.sp
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 32.sp,
+ lineHeight = 40.sp
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 28.sp,
+ lineHeight = 36.sp
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 24.sp,
+ lineHeight = 32.sp
+ ),
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 22.sp,
+ lineHeight = 28.sp
+ ),
+ titleMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.15.sp
+ ),
+ titleSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp
+ ),
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.25.sp
+ ),
+ bodySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.4.sp
+ ),
+ labelLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp
+ ),
+ labelMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+)
+
diff --git a/app/src/main/java/com/project/newplanet/MainActivity.kt b/app/src/main/java/com/project/newplanet/MainActivity.kt
deleted file mode 100644
index 2eb74e6..0000000
--- a/app/src/main/java/com/project/newplanet/MainActivity.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.project.newplanet
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.project.newplanet.ui.theme.NewPlanetTheme
-
-class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- NewPlanetTheme {
- Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
- Greeting(
- name = "Android",
- modifier = Modifier.padding(innerPadding)
- )
- }
- }
- }
- }
-}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- NewPlanetTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/project/newplanet/ui/theme/Color.kt b/app/src/main/java/com/project/newplanet/ui/theme/Color.kt
deleted file mode 100644
index fc09976..0000000
--- a/app/src/main/java/com/project/newplanet/ui/theme/Color.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.project.newplanet.ui.theme
-
-import androidx.compose.ui.graphics.Color
-
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
-
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/app/src/main/java/com/project/newplanet/ui/theme/Theme.kt b/app/src/main/java/com/project/newplanet/ui/theme/Theme.kt
deleted file mode 100644
index 97b5682..0000000
--- a/app/src/main/java/com/project/newplanet/ui/theme/Theme.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.project.newplanet.ui.theme
-
-import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
-
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
-)
-
-private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
-)
-
-@Composable
-fun NewPlanetTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
- content: @Composable () -> Unit
-) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
-
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
- }
-
- MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/project/newplanet/ui/theme/Type.kt b/app/src/main/java/com/project/newplanet/ui/theme/Type.kt
deleted file mode 100644
index 9e6c2b3..0000000
--- a/app/src/main/java/com/project/newplanet/ui/theme/Type.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.project.newplanet.ui.theme
-
-import androidx.compose.material3.Typography
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.sp
-
-// Set of Material typography styles to start with
-val Typography = Typography(
- bodyLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp,
- lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
- /* Other default text styles to override
- titleLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
- ),
- labelSmall = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Medium,
- fontSize = 11.sp,
- lineHeight = 16.sp,
- letterSpacing = 0.5.sp
- )
- */
-)
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0d38447..59eea5c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- NewPlanet
+ Новая Планета
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 952b930..2be3a46 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,4 +3,6 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
+ alias(libs.plugins.kotlin.serialization) apply false
+ alias(libs.plugins.hilt) apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5d464bd..e13ffcf 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,8 +6,20 @@ junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.9.4"
+lifecycleViewmodel = "2.9.4"
activityCompose = "1.11.0"
composeBom = "2024.09.00"
+hilt = "2.53"
+hiltNavigationCompose = "1.2.0"
+room = "2.6.1"
+retrofit = "2.11.0"
+okhttp = "4.12.0"
+kotlinxSerialization = "1.7.3"
+kotlinxSerializationConverter = "1.0.0"
+navigation = "2.8.4"
+coil = "2.7.0"
+lottie = "6.1.0"
+coroutines = "1.10.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -15,6 +27,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodel" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
@@ -25,8 +38,43 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+# Hilt
+hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
+hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
+hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
+
+# Room
+room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
+room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
+room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
+
+# Retrofit
+retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
+retrofit-kotlinx-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinxSerializationConverter" }
+okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
+okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
+
+# Kotlinx Serialization
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
+
+# Navigation
+navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
+
+# Coil
+coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
+
+# Lottie
+lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" }
+
+# Coroutines
+kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
+kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
+kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }