Initial commit2
This commit is contained in:
189
README_NEW_PLANET.md
Normal file
189
README_NEW_PLANET.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# Новая Планета — Frontend (Android)
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h3>Визуальное расписание для детей с РАС</h3>
|
||||||
|
<p>Мобильное приложение на Android с ИИ-помощником "Планета Земля"</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 О проекте
|
||||||
|
|
||||||
|
**Новая Планета** — 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<LoginResponse>
|
||||||
|
|
||||||
|
@GET("api/v1/schedules")
|
||||||
|
suspend fun getSchedules(
|
||||||
|
@Header("Authorization") token: String
|
||||||
|
): Response<List<Schedule>>
|
||||||
|
|
||||||
|
@POST("api/v1/ai/chat")
|
||||||
|
suspend fun chatWithAI(
|
||||||
|
@Header("Authorization") token: String,
|
||||||
|
@Body request: ChatRequest
|
||||||
|
): Response<ChatResponse>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Команда
|
||||||
|
|
||||||
|
Разработано с ❤️ командой **Новая Планета**
|
||||||
@@ -2,20 +2,27 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.hilt)
|
||||||
|
id("org.jetbrains.kotlin.kapt")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.project.newplanet"
|
namespace = "com.novayaplaneta"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.project.newplanet"
|
applicationId = "com.novayaplaneta"
|
||||||
minSdk = 30
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
vectorDrawables {
|
||||||
|
useSupportLibrary = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -28,27 +35,72 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
packaging {
|
||||||
|
resources {
|
||||||
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Core Android
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
|
||||||
|
// Compose
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
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)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".NewPlanetApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|||||||
55
app/src/main/java/com/novayaplaneta/MainActivity.kt
Normal file
55
app/src/main/java/com/novayaplaneta/MainActivity.kt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package com.novayaplaneta
|
||||||
|
|
||||||
|
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.ui.Modifier
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.novayaplaneta.ui.components.BottomNavigationBar
|
||||||
|
import com.novayaplaneta.ui.navigation.NewPlanetNavigation
|
||||||
|
import com.novayaplaneta.ui.theme.NewPlanetTheme
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
NewPlanetTheme {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val currentRoute = navController.currentDestination?.route
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
bottomBar = {
|
||||||
|
BottomNavigationBar(
|
||||||
|
currentRoute = currentRoute,
|
||||||
|
onNavigate = { route ->
|
||||||
|
navController.navigate(route) {
|
||||||
|
popUpTo(navController.graph.startDestinationId) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
NewPlanetNavigation(
|
||||||
|
navController = navController,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.novayaplaneta
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class NewPlanetApplication : Application()
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<ChatMessageEntity>>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertMessage(message: ChatMessageEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM chat_messages WHERE userId = :userId")
|
||||||
|
suspend fun deleteMessagesByUserId(userId: String)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<RewardEntity>>
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<ScheduleEntity>>
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<TaskEntity>>
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<UserEntity?>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertUser(user: UserEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users")
|
||||||
|
suspend fun deleteAllUsers()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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?
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Task> = 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<LoginResponse>
|
||||||
|
|
||||||
|
@POST("api/v1/ai/chat")
|
||||||
|
suspend fun chatWithAI(
|
||||||
|
@Header("Authorization") token: String,
|
||||||
|
@Body request: ChatRequest
|
||||||
|
): Response<ChatResponse>
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.novayaplaneta.data.remote.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatRequest(
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.novayaplaneta.data.remote.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatResponse(
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.novayaplaneta.data.remote.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LoginRequest(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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<String> {
|
||||||
|
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<List<ChatMessage>> {
|
||||||
|
return chatMessageDao.getMessagesByUserId(userId).map { messages ->
|
||||||
|
messages.map { it.toDomain() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun generateSchedule(userId: String, preferences: String): Result<String> {
|
||||||
|
// TODO: Implement schedule generation
|
||||||
|
return Result.failure(Exception("Not implemented"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<User> {
|
||||||
|
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<User?> {
|
||||||
|
return userDao.getCurrentUser().map { it?.toDomain() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveUser(user: User) {
|
||||||
|
userDao.insertUser(user.toEntity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Reward>> {
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Schedule>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Task>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
57
app/src/main/java/com/novayaplaneta/di/DatabaseModule.kt
Normal file
57
app/src/main/java/com/novayaplaneta/di/DatabaseModule.kt
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
60
app/src/main/java/com/novayaplaneta/di/NetworkModule.kt
Normal file
60
app/src/main/java/com/novayaplaneta/di/NetworkModule.kt
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
53
app/src/main/java/com/novayaplaneta/di/RepositoryModule.kt
Normal file
53
app/src/main/java/com/novayaplaneta/di/RepositoryModule.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
14
app/src/main/java/com/novayaplaneta/domain/model/Reward.kt
Normal file
14
app/src/main/java/com/novayaplaneta/domain/model/Reward.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
14
app/src/main/java/com/novayaplaneta/domain/model/Schedule.kt
Normal file
14
app/src/main/java/com/novayaplaneta/domain/model/Schedule.kt
Normal file
@@ -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<Task>,
|
||||||
|
val date: LocalDateTime,
|
||||||
|
val createdAt: LocalDateTime,
|
||||||
|
val userId: String
|
||||||
|
)
|
||||||
|
|
||||||
15
app/src/main/java/com/novayaplaneta/domain/model/Task.kt
Normal file
15
app/src/main/java/com/novayaplaneta/domain/model/Task.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
16
app/src/main/java/com/novayaplaneta/domain/model/User.kt
Normal file
16
app/src/main/java/com/novayaplaneta/domain/model/User.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<String>
|
||||||
|
fun getChatHistory(userId: String): Flow<List<ChatMessage>>
|
||||||
|
suspend fun generateSchedule(userId: String, preferences: String): Result<String>
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<User>
|
||||||
|
suspend fun logout()
|
||||||
|
fun getCurrentUser(): Flow<User?>
|
||||||
|
suspend fun saveUser(user: User)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Reward>>
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Schedule>>
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Task>>
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<List<Schedule>> {
|
||||||
|
return repository.getSchedules(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<String> {
|
||||||
|
return repository.sendMessage(userId, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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("Отправить")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<AIUiState> = _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<com.novayaplaneta.domain.model.ChatMessage> = emptyList(),
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val error: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<RewardsUiState> = _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<com.novayaplaneta.domain.model.Reward> = emptyList(),
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<ScheduleUiState> = _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<com.novayaplaneta.domain.model.Schedule> = emptyList(),
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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("Выйти")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<SettingsUiState> = _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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<TaskUiState> = _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<com.novayaplaneta.domain.model.Task> = emptyList(),
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<TimerUiState> = _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
|
||||||
|
)
|
||||||
|
|
||||||
26
app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
Normal file
26
app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
Normal file
@@ -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)
|
||||||
|
|
||||||
49
app/src/main/java/com/novayaplaneta/ui/theme/Theme.kt
Normal file
49
app/src/main/java/com/novayaplaneta/ui/theme/Theme.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
110
app/src/main/java/com/novayaplaneta/ui/theme/Type.kt
Normal file
110
app/src/main/java/com/novayaplaneta/ui/theme/Type.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">NewPlanet</string>
|
<string name="app_name">Новая Планета</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -3,4 +3,6 @@ plugins {
|
|||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
|
alias(libs.plugins.hilt) apply false
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,20 @@ junit = "4.13.2"
|
|||||||
junitVersion = "1.3.0"
|
junitVersion = "1.3.0"
|
||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
lifecycleRuntimeKtx = "2.9.4"
|
lifecycleRuntimeKtx = "2.9.4"
|
||||||
|
lifecycleViewmodel = "2.9.4"
|
||||||
activityCompose = "1.11.0"
|
activityCompose = "1.11.0"
|
||||||
composeBom = "2024.09.00"
|
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]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
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-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
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-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-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
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-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", 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" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user