commit d31bce2f898c4f683c632f29cb6787d86b4e6f5d Author: Ernest Litvinenko Date: Wed Jun 26 16:50:29 2024 +0300 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..47f8e00 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0ad17cb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..a69613c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,79 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlinx.serialization) +} + +android { + namespace = "com.example.mpdriver" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.mpdriver" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.navigation.compose) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + implementation(libs.kotlinx.serialization.json) + implementation(libs.okhttp) + implementation(libs.gson) + implementation(libs.mmkv) + implementation(libs.kotlinx.datetime) + implementation(libs.yandex.maps) + implementation(libs.kotlin.coroutines) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/mpdriver/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/mpdriver/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..4d87741 --- /dev/null +++ b/app/src/androidTest/java/com/example/mpdriver/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mpdriver + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.mpdriver", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f0cff39 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/MainActivity.kt b/app/src/main/java/com/example/mpdriver/MainActivity.kt new file mode 100644 index 0000000..9aaca82 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/MainActivity.kt @@ -0,0 +1,364 @@ +package com.example.mpdriver + +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.sharp.Settings +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.example.mpdriver.api.Api +import com.example.mpdriver.recievers.TimeTickReciever +import com.example.mpdriver.screens.Feed +import com.example.mpdriver.screens.MapScreen +import com.example.mpdriver.screens.PhoneCodeInputScreen +import com.example.mpdriver.screens.PhoneInputScreen +import com.example.mpdriver.screens.SubtaskScreen +import com.example.mpdriver.screens.TasksList +import com.example.mpdriver.storage.Database +import com.tencent.mmkv.MMKV +import com.yandex.mapkit.MapKitFactory + + +class MainActivity : ComponentActivity() { + val timeTickReciever = TimeTickReciever() + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + MapKitFactory.setApiKey("f4385b18-0740-454a-a71f-d20da7e8fc3b") + MapKitFactory.initialize(this) + val rootDir = MMKV.initialize(this) + println("mmkv root: $rootDir") + registerReceiver(timeTickReciever, IntentFilter(Intent.ACTION_TIME_TICK)) + + enableEdgeToEdge() + setContent { + AuthNavigator() + } + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(timeTickReciever) + } +} + +@Composable +fun AuthNavigator() { + val authNavigator = rememberNavController() + Api.setNavHostController(authNavigator) + val startDestination = if (Database.access_token == null) "auth" else "home" + NavHost(navController = authNavigator, startDestination = startDestination) { + composable("auth") { + PhoneInputScreen(navHostController = authNavigator) + } + composable("auth/code") { + PhoneCodeInputScreen(navHostController = authNavigator) + } + composable("home") { + MainNavigator() + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainNavigator() { + var headerTitle by remember { + mutableStateOf("Лента") + } + var backLink by remember { + mutableStateOf(false) + } + + val bottomNavController = rememberNavController() + Scaffold( + topBar = { + Header( + hostController = bottomNavController, + title = headerTitle, + backLink = backLink + ) + }, + bottomBar = { Footer( + hostController = bottomNavController + ) }, + ) { + + + Box(modifier = Modifier.padding(it)) { + NavHost( + navController = bottomNavController, + startDestination = "feed", + ) { + composable("feed", + exitTransition = { + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) + } + ) { + backLink = false + headerTitle = "Лента" + Box(modifier = Modifier + .fillMaxWidth() + .fillMaxHeight()) { + Feed(hostController = bottomNavController) + } + } + composable("feed/planned-tasks", + + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) + } + + ) { + backLink = true + headerTitle = "Задачи" + Box(modifier = Modifier + .fillMaxWidth() + .fillMaxHeight()) { + TasksList(hostController = bottomNavController) + } + } + composable("events") { + backLink = false + headerTitle = "События" + + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { + Text(text = "События") + } + + } + composable("settings") { + backLink = false + headerTitle = "Настройки" + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { + Text(text = "Profile") + } + } + composable("notifications") { + backLink = false + headerTitle = "Уведомления" + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { + Text(text = "Notifications") + } + } + composable("chat") { + backLink = false + headerTitle = "Чат" + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { + Text(text = "Chat") + } + } + composable("map", + enterTransition = { + fadeIn() + }, + exitTransition = { + ExitTransition.None + }) { + backLink = false + headerTitle = "Карта" + MapScreen() + } + composable("tasks/{taskId}", arguments = listOf(navArgument("taskId") { + type = NavType.LongType + }), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) + } + ) {bse -> + bse.arguments?.let {args -> + headerTitle = "Детали задачи" + backLink = true + Box(modifier = Modifier + .fillMaxWidth() + .fillMaxHeight()){ + SubtaskScreen(args.getLong("taskId")) + } + return@composable + } + + + } + } + } + } +} + +@Composable +fun Header( + modifier: Modifier = Modifier, + title: String = "Лента", + backLink: Boolean = false, + hostController: NavHostController +) { + Row( + modifier = modifier + .shadow(10.dp, RoundedCornerShape(10.dp)) + .fillMaxWidth() + .background(Color.White) + .clip(RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp)) + .height(100.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + if (backLink) { + IconButton(onClick = { hostController.navigateUp() }) { + Icon(Icons.Filled.ArrowBack, contentDescription = "Back") + } + } + Text( + text = title, + modifier = Modifier, + fontSize = 30.sp, + fontWeight = FontWeight.SemiBold + ) + if (!backLink) { + TextButton( + onClick = { /*TODO*/ }, + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Black, + ) + ) { + Icon( + Icons.Sharp.Settings, + contentDescription = "Settings", + ) + } + } else { + Spacer(modifier = Modifier.weight(1f)) + } + + + } + } +} + +data class FooterNavMenuLabel( + val title: String, + val defaultImage: Int, + val imageActive: Int +) + +@Composable +fun Footer(modifier: Modifier = Modifier, + hostController: NavHostController +) { + + val menuItems = mapOf( + "feed" to FooterNavMenuLabel("Лента", R.drawable.home_default, R.drawable.home), + "events" to FooterNavMenuLabel("События", R.drawable.calendar_default, R.drawable.calendar), + "notifications" to FooterNavMenuLabel("Уведомления", R.drawable.bell_default, R.drawable.bell), + "chat" to FooterNavMenuLabel("Чат", R.drawable.chat_default, R.drawable.chat), + "map" to FooterNavMenuLabel("Карта", R.drawable.location_default, R.drawable.location) + ) + + var activeRoute by remember { + mutableStateOf("feed") + } + + Row( + modifier = modifier + .shadow(10.dp, RoundedCornerShape(10.dp)) + .fillMaxWidth() + .background(Color.White) + .clip(RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp)) + .padding(horizontal = 5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 40.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + menuItems.forEach { + TextButton( + onClick = { + activeRoute = it.key + hostController.navigate(it.key) + }, + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Black, + ) + ) { + Column (horizontalAlignment = Alignment.CenterHorizontally) { + if (activeRoute== it.key) { + Image(painter = painterResource(id = it.value.imageActive), contentDescription = "") + Text(text = it.value.title, fontSize = 11.sp, color = Color.Black) + } else { + Image(painter = painterResource(id = it.value.defaultImage), contentDescription = "") + Text(text = it.value.title, fontSize = 11.sp, color = Color.Gray) + } + + } + } + + } + } + } +} + + + + diff --git a/app/src/main/java/com/example/mpdriver/NotificationApplication.kt b/app/src/main/java/com/example/mpdriver/NotificationApplication.kt new file mode 100644 index 0000000..50a242c --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/NotificationApplication.kt @@ -0,0 +1,21 @@ +package com.example.mpdriver + +import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService + +class NotificationApplication: Application() { + @RequiresApi(Build.VERSION_CODES.O) + override fun onCreate() { + super.onCreate() + val notificationChannel = NotificationChannel("mpdriver_notifications", + "MPDriver notification channel", + NotificationManager.IMPORTANCE_HIGH) + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(notificationChannel) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/NotificationService.kt b/app/src/main/java/com/example/mpdriver/NotificationService.kt new file mode 100644 index 0000000..424e607 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/NotificationService.kt @@ -0,0 +1,27 @@ +package com.example.mpdriver + +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Notifications +import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.IconCompat +import kotlin.random.Random + + +class NotificationService(private val context: Context) { + val manager = context.getSystemService(NotificationManager::class.java) + + fun showNotificationAuthCode(code: String) { + val notification = NotificationCompat.Builder(context, "mpdriver_notifications") + .setContentTitle("MPDriver - код подтверждения") + .setContentText("Ваш код подтверждения: $code") + .setPriority(NotificationManager.IMPORTANCE_HIGH) + .setSmallIcon(R.drawable.tick_default) + .setAutoCancel(true) + .build() + + manager.notify(Random.nextInt(), notification) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/api/Api.kt b/app/src/main/java/com/example/mpdriver/api/Api.kt new file mode 100644 index 0000000..21cd6d8 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/api/Api.kt @@ -0,0 +1,88 @@ +package com.example.mpdriver.api + +import android.annotation.SuppressLint +import android.content.Context +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.example.mpdriver.storage.Database +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.tencent.mmkv.MMKV +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.internal.EMPTY_REQUEST +import java.io.IOException +import java.lang.reflect.Type + + +data class Langs( + val ru: String? +) + +fun T.toJson(): String { + return Gson().toJson(this) +} + +inline fun Response.parse(): T { + val data = this.body!!.string() + return Gson().fromJson(data, T::class.java) +} + +inline fun Response.parseList(): List { + val data = this.body!!.string() + val type: Type = object : TypeToken>() {}.type + return Gson().fromJson(data, type) +} + + +open class Api(val ctx: Context) { + val kv = MMKV.defaultMMKV() + + val clientCheckAuth = OkHttpClient.Builder().build() + + val client = OkHttpClient.Builder() + .addInterceptor { chain -> + val req = chain.request().newBuilder() + .addHeader("Authorization", "Bearer ${Database.access_token}") + .build() + chain.proceed(req) + } + .build() + + + val BASE_URL = "http://147.45.107.119:8000/api/v1" + + fun performRequest(clientReq: Request, errorHandler: (Exception) -> Unit = {}, handler: (Response) -> Unit) { + client.newCall(clientReq).enqueue(object : Callback { + override fun onFailure(call: okhttp3.Call, e: java.io.IOException) { + errorHandler(e) + } + + override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) { + if (response.code == 401) { + Database.access_token = null + hostController?.let { hc -> + MainScope().launch { + hc.navigate("auth") + } + } + } + handler(response) + } + }) + } + + companion object { + var hostController: NavHostController? = null + + fun setNavHostController(hc: NavHostController) { + hostController = hc + } + } +} + diff --git a/app/src/main/java/com/example/mpdriver/api/Auth.kt b/app/src/main/java/com/example/mpdriver/api/Auth.kt new file mode 100644 index 0000000..be090c2 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/api/Auth.kt @@ -0,0 +1,86 @@ +package com.example.mpdriver.api + +import android.content.Context +import android.widget.Toast +import com.example.mpdriver.storage.Database +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import okhttp3.Call +import okhttp3.Callback +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import java.io.IOException + +data class GetPhoneCodeRequest( + val phoneNumber: String +) + +enum class ErrorCodes { + @SerializedName("SotrNotFounded") + SOTR_NOT_FOUNDED, + @SerializedName("IncorrectPhone") + INCORRECT_PHONE +} + + +data class GetPhoneCodeResponse( + val code: Int?, + val error: ErrorCodes?, + val status: Int?, + val detail: String?, + val langs: Langs? +) + +data class GetTokenResponse( + @SerializedName("access_token") val accessToken: String?, + @SerializedName("token_type") val tokenType: String? +) + + +class Auth(ctx: Context) : Api(ctx) { + + fun getPhoneCode(phone: String, errorHandler: (IOException)-> Unit = {}, handler: (GetPhoneCodeResponse) -> Unit) { + Database.phoneNumber = phone + val body = GetPhoneCodeRequest(phone).toJson().toRequestBody("application/json".toMediaType()) + val req = Request.Builder() + .url("${BASE_URL}/auth/phone") + .post(body) + .build() + client.newCall(req).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + println(e) + errorHandler(e) + } + + override fun onResponse(call: Call, response: Response) { + handler(response.parse()) + } + }) + } + + fun getToken(phone: String, code: String, errorHandler: (IOException)-> Unit = {}, handler: (GetTokenResponse) -> Unit) { + val body = MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("username", phone).addFormDataPart("password", code).build() + val req = Request.Builder() + .url("${BASE_URL}/auth/phone/code") + .post(body) + .build() + client.newCall(req).enqueue(object: Callback { + override fun onFailure(call: Call, e: IOException) { + errorHandler(e) + } + + override fun onResponse(call: Call, response: Response) { + val parsedResponse = response.parse() + Database.access_token = parsedResponse.accessToken + handler(parsedResponse) + } + + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/api/Task.kt b/app/src/main/java/com/example/mpdriver/api/Task.kt new file mode 100644 index 0000000..e836f3a --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/api/Task.kt @@ -0,0 +1,191 @@ +package com.example.mpdriver.api + +import android.content.Context +import android.util.Log +import com.example.mpdriver.storage.Database +import com.google.gson.annotations.SerializedName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody + + +data class SetTaskStatusRequest( + val task_id: Long +) + +data class TaskResponse( + + @SerializedName("id" ) var id: Long? = null, + @SerializedName("startPln" ) var startPln: String? = null, + @SerializedName("endPln" ) var endPln: String? = null, + @SerializedName("startFact" ) var startFact: String? = null, + @SerializedName("endFact" ) var endFact: String? = null, + @SerializedName("status" ) var status: String? = null, + @SerializedName("taskType" ) var taskType: String? = null, + @SerializedName("text" ) var text: String? = null, + @SerializedName("events" ) var events: ArrayList = arrayListOf(), + @SerializedName("subtasks" ) var subtasks: ArrayList = arrayListOf(), + @SerializedName("route" ) var route: Route? = Route() + +) + +data class Events ( + + @SerializedName("id" ) var id : Long? = null, + @SerializedName("type" ) var type : String? = null, + @SerializedName("text" ) var text : String? = null, + @SerializedName("eventDatetime" ) var eventDatetime : String? = null + +) + +data class Location ( + + @SerializedName("lat" ) var lat : Double? = null, + @SerializedName("lon" ) var lon : Double? = null + +) + +data class Station ( + + @SerializedName("id" ) var id : Long? = null, + @SerializedName("name" ) var name : String? = null, + @SerializedName("location" ) var location : Location? = Location() + +) + +data class Subtasks ( + + @SerializedName("id" ) var id : Long? = null, + @SerializedName("parentId" ) var parentId : Long? = null, + @SerializedName("startPln" ) var startPln : String? = null, + @SerializedName("endPln" ) var endPln : String? = null, + @SerializedName("startFact" ) var startFact : String? = null, + @SerializedName("endFact" ) var endFact : String? = null, + @SerializedName("status" ) var status : String? = null, + @SerializedName("taskType" ) var taskType : String? = null, + @SerializedName("text" ) var text : String? = null, + @SerializedName("station" ) var station : Station? = Station() + +) + +data class TRS ( + + @SerializedName("id" ) var id : Long? = null, + @SerializedName("gost" ) var gost : String? = null + +) + +data class Route ( + + @SerializedName("id" ) var id : Long? = null, + @SerializedName("temperatureProperty" ) var temperatureProperty : Int? = null, + @SerializedName("name" ) var name : String? = null, + @SerializedName("trailer" ) var trailer : TRS? = null, + @SerializedName("truck" ) var truck : TRS? = null + +) + + +class TaskApi(ctx: Context): Api(ctx) { + + suspend fun getPlannedTasksApiCall(errorHandler: (Exception)-> Unit = {}, handler: (List) -> Unit) { + val req = Request.Builder() + .url("$BASE_URL/tasks/planned") + .build() + + performRequest(req, errorHandler) { + handler(it.parseList()) + } + } + + fun getPlannedTasks(errorHandler: (Exception)-> Unit = {}, handler: (List) -> Unit) { + runBlocking { + launch(Dispatchers.IO) { + getPlannedTasksApiCall { + println(Database.planned_tasks) + println(it) + Database.planned_tasks = it + println(Database.planned_tasks) + } + } + handler(Database.planned_tasks) + } + } + + suspend fun getActiveTaskApiCall(errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { + val req = Request.Builder() + .url("$BASE_URL/tasks/active") + .build() + + performRequest(req, errorHandler) { + handler(it.parse()) + } + } + fun getActiveTask(errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { + runBlocking { + launch(Dispatchers.IO) { + getAllTasksForUser() + } + + val data = Database.tasks.filter { it.status == "InProgress" } + if (data.count() >= 1) { + return@runBlocking handler(data[0]) + } + handler(TaskResponse()) + } + } + + fun getCompletedTask(errorHandler: (Exception)-> Unit = {}, handler: (List) -> Unit) { + val req = Request.Builder() + .url("$BASE_URL/tasks/completed") + .build() + + performRequest(req, errorHandler) { + handler(it.parseList()) + } + } + fun getSubtasks(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (List) -> Unit) { + val req = Request.Builder() + .url("$BASE_URL/tasks/$taskId/subtasks") + .build() + + performRequest(req, errorHandler) { + handler(it.parseList()) + } + } + fun getEvents(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (List) -> Unit) { + val req = Request.Builder() + .url("$BASE_URL/tasks/$taskId/events") + .build() + + performRequest(req, errorHandler) { + handler(it.parseList()) + } + } + + fun getAllTasksForUser() { + val req = Request.Builder().url("$BASE_URL/tasks").build() + performRequest(req) {res -> + val data = res.parseList() + Database.tasks = data + } + } + + fun setTaskStatusInProgress(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { + + val body = SetTaskStatusRequest(taskId).toJson().toRequestBody("application/json".toMediaType()) + + val req = Request.Builder() + .url("$BASE_URL/tasks/active") + .post(body) + + performRequest(req.build(), errorHandler) { + handler(it.parse()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/Buttons.kt b/app/src/main/java/com/example/mpdriver/components/Buttons.kt new file mode 100644 index 0000000..0bf3266 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/Buttons.kt @@ -0,0 +1,89 @@ +package com.example.mpdriver.components + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + + +enum class ButtonType { + DANGER, + WARNING, + SUCCESS, + DEFAULT +} + +@Composable +fun ActiveButton(onClick: () -> Unit, modifier: Modifier = Modifier, text: String) { + Button( + modifier = modifier, + onClick = onClick, + colors = ButtonDefaults.buttonColors(containerColor = Color.Black), + shape = RoundedCornerShape(10.dp) + ) { + Text(text = text) + } +} + +@Composable +fun StaleButton(onClick: () -> Unit, modifier: Modifier = Modifier, text: String) { + Button( + modifier = modifier, + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFFF2F2F2), + contentColor = Color.Black + ), + shape = RoundedCornerShape(10.dp) + ) { + Text(text = text) + } +} + + +@Composable +fun IteractionButton(onClick: () -> Unit, child: @Composable () -> Unit) { + Button(onClick = onClick, shape = RectangleShape, contentPadding = PaddingValues(0.dp), colors = ButtonDefaults.buttonColors(contentColor = Color.Black, containerColor = Color.Transparent)) { + child() + } +} + + +@Preview(showBackground = true) +@Composable +fun JDEButton(modifier: Modifier = Modifier, + onClick: () -> Unit = {}, + type: ButtonType = ButtonType.DEFAULT, + content: @Composable () -> Unit = { Text( + text = "Button", +)}) { + Button(onClick = onClick, + colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent, contentColor = Color.Black), contentPadding = PaddingValues(0.dp), shape = RectangleShape + ) { + Row(modifier + .fillMaxWidth() + .border(2.dp, + when(type) { + ButtonType.DANGER -> Color(0xFFE5332A) + ButtonType.WARNING -> Color(0xFFFFC700) + ButtonType.SUCCESS -> Color(0xFF45900B) + ButtonType.DEFAULT -> Color.Gray + }, + RoundedCornerShape(10.dp)) + .padding(15.dp)) { + content() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/ComposableLifecycle.kt b/app/src/main/java/com/example/mpdriver/components/ComposableLifecycle.kt new file mode 100644 index 0000000..90cf249 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/ComposableLifecycle.kt @@ -0,0 +1,23 @@ +package com.example.mpdriver.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner + +@Composable +fun ComposableLifecycle(modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onEvent: (LifecycleOwner, Lifecycle.Event) -> Unit) { + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver {source, event -> + onEvent(source, event) + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } +} + diff --git a/app/src/main/java/com/example/mpdriver/components/EmptyList.kt b/app/src/main/java/com/example/mpdriver/components/EmptyList.kt new file mode 100644 index 0000000..d0e337b --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/EmptyList.kt @@ -0,0 +1,40 @@ +package com.example.mpdriver.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.mpdriver.R + + +@Composable +fun EmptyList(modifier: Modifier = Modifier, text: String = "") { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + ) { + Image( + painter = painterResource(id = R.drawable.zoom_icon), + contentDescription = "No Active Task", + modifier = Modifier + .fillMaxWidth() + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = text, + fontSize = 16.sp, + textAlign = TextAlign.Center + ) + } + return +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/HeaderTabs.kt b/app/src/main/java/com/example/mpdriver/components/HeaderTabs.kt new file mode 100644 index 0000000..87d7777 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/HeaderTabs.kt @@ -0,0 +1,51 @@ +package com.example.mpdriver.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +data class HeaderTabsData( + val idx: Int, + val title: String +) + +@Composable +fun HeaderTabs( + modifier: Modifier = Modifier, + tabsData: List, + activeTab: Int = 0, + setActiveTab: (Int) -> Unit +) { + Row( + modifier + .fillMaxWidth() + .padding(bottom = 15.dp), horizontalArrangement = Arrangement.SpaceBetween + ) { + tabsData.forEachIndexed { idx, it -> + when (activeTab) { + it.idx -> ActiveButton( + modifier = Modifier.weight(1f), + onClick = { + setActiveTab(it.idx) + }, + text = it.title + ) + else -> StaleButton( + modifier = Modifier.weight(1f), + onClick = { setActiveTab(it.idx) }, + text = it.title + ) + } + if (idx != 0 || idx != tabsData.count() - 1) { + Spacer(modifier = Modifier.width(10.dp)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/InformationPlaceholder.kt b/app/src/main/java/com/example/mpdriver/components/InformationPlaceholder.kt new file mode 100644 index 0000000..380dded --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/InformationPlaceholder.kt @@ -0,0 +1,90 @@ +package com.example.mpdriver.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + + +@Preview(showBackground = true) +@Composable +fun InformationPlaceholderBig( + modifier: Modifier = Modifier, + mainText: String = "main text", + subText: String = "Subtext" +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFFEEEEEE)) + .padding(vertical = 12.dp) + .clip(RoundedCornerShape(10.dp)), + horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally + + ) { + Text( + text = mainText, + fontSize = 22.sp, + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold + ) + Text( + text = subText, + fontSize = 15.sp, + fontWeight = androidx.compose.ui.text.font.FontWeight.Normal, + color = Color.Gray + ) + } +} + +@Preview(showBackground = true) +@Composable +fun InformationPlaceholderSmall( + modifier: Modifier = Modifier, + mainText: String = "main text", + subText: String = "Subtext", + children: @Composable () -> Unit = {} +) { + Row( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFFEEEEEE)) + .padding(vertical = 12.dp, horizontal = 10.dp) + .clip(RoundedCornerShape(10.dp)), + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + horizontalAlignment = Alignment.Start + ) { + Text( + text = subText, + fontSize = 15.sp, + fontWeight = androidx.compose.ui.text.font.FontWeight.Normal, + color = Color.Gray + ) + Text( + text = mainText, + fontSize = 22.sp, + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold + ) + + } + children() + } + +} + + diff --git a/app/src/main/java/com/example/mpdriver/components/Layout.kt b/app/src/main/java/com/example/mpdriver/components/Layout.kt new file mode 100644 index 0000000..f7ab9fc --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/Layout.kt @@ -0,0 +1,29 @@ +package com.example.mpdriver.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +@Composable +fun Layout(modifier: Modifier = Modifier, header: @Composable () -> Unit = {}, dataList: List, state : LazyListState = rememberLazyListState(), itemComponent: @Composable (T) -> Unit) { + LazyColumn( + modifier + .fillMaxWidth() + .padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp), state = state) { + item { + header() + } + items(items = dataList) { + itemComponent(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/Subtask.kt b/app/src/main/java/com/example/mpdriver/components/Subtask.kt new file mode 100644 index 0000000..bcba9ce --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/Subtask.kt @@ -0,0 +1,545 @@ +package com.example.mpdriver.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusEvent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.example.mpdriver.R +import com.example.mpdriver.api.Subtasks +import com.example.mpdriver.recievers.TimeTickReciever +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.UtcOffset +import kotlinx.datetime.asTimeZone +import kotlinx.datetime.format.byUnicodePattern +import kotlinx.datetime.toInstant +import kotlinx.datetime.until +import kotlin.math.abs + +@Preview(showBackground = true) +@Composable +fun Subtask( + modifier: Modifier = Modifier, + children: @Composable () -> Unit = {}, + subtask: Subtasks = Subtasks(1, startPln = "2024-06-01T00:00", endPln = "2024-06-10T00:00"), + footerButton: @Composable () -> Unit = {} +) { + + var isActionVisible by remember { + mutableStateOf(false) + } + + var expanded by remember { + mutableStateOf(true) + } + + var nowTime by remember { + mutableStateOf(Clock.System.now()) + } + + TimeTickReciever.registerHandler { + nowTime = Clock.System.now() + } + + val startPln = LocalDateTime.parse(subtask.startPln!!) + val endPln = LocalDateTime.parse(subtask.endPln!!) + + val dateFormat = LocalDateTime.Format { + byUnicodePattern("d.MM.yyyy") + } + + val timeFormat = LocalDateTime.Format { + byUnicodePattern("HH:mm") + } + + val leastBase = nowTime.until( + endPln.toInstant(offset = UtcOffset(3)), + timeZone = UtcOffset(3).asTimeZone(), unit = DateTimeUnit.MINUTE + ) + + val isDelay = leastBase != abs(leastBase) + val leastDays = abs(leastBase) / 60 / 24 + val leastHours = abs(leastBase) / 60 - leastDays * 24 + val leastMinutes = abs(leastBase) - leastDays * 60 * 24 - leastHours * 60 + + + val status = when { + subtask.status == "Completed" && !isDelay -> TaskStatus.SUCCESS + subtask.status == "Completed" && isDelay -> TaskStatus.WARNING + subtask.status == "Cancelled" -> TaskStatus.WARNING + subtask.status == "InProgress" || subtask.status == "NotDefined" && isDelay -> TaskStatus.DANGER + else -> TaskStatus.DEFAULT + } + + + IteractionButton(onClick = { + isActionVisible = true + }) { + Column( + modifier + .border( + 2.dp, when (status) { + TaskStatus.DEFAULT -> Color.Gray + TaskStatus.SUCCESS -> Color(0xFF45900B) + TaskStatus.DANGER -> Color(0xFFE5332A) + TaskStatus.WARNING -> Color(0xFFFFC700) + }, RoundedCornerShape(10.dp) + ) + .fillMaxWidth() + .padding(horizontal = 15.dp, vertical = 15.dp) + ) { + Row( + Modifier + .fillMaxWidth() + .padding(bottom = 15.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(1f), + text = "${subtask.text}", + fontWeight = FontWeight.Bold, + fontSize = 20.sp + ) + Spacer(modifier = Modifier.width(10.dp)) + + IconButton(onClick = { /*TODO*/ }, modifier = Modifier.weight(0.1f)) { + Image( + painter = painterResource(id = R.drawable.tick_default), + contentDescription = "tick" + ) + } + } + + Text( + modifier = Modifier.padding(bottom = 5.dp), + text = "Подзадача доожна быть выполнена: ", + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier.weight(2f), + mainText = dateFormat.format(endPln), + subText = "Дата" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier.weight(1f), + mainText = timeFormat.format(endPln), + subText = "Время" + ) + } + Text( + modifier = Modifier.padding(bottom = 5.dp), + text = if (isDelay) "Задержка" else "Осталось времени:", + fontWeight = FontWeight.Bold, + fontSize = if (isDelay) 20.sp else 14.sp, + color = if (isDelay) Color(0xFFE5332A) else Color.Black + ) + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastDays.toString(), + subText = "Дни" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastHours.toString(), + subText = "Часы" + ) + + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastMinutes.toString(), + subText = "Минуты" + ) + } + + TextButton( + onClick = { /*TODO*/ }, + colors = ButtonDefaults.buttonColors( + contentColor = Color.Black, + containerColor = Color.Transparent + ) + ) { + Row( + Modifier + .fillMaxWidth() + .border(1.dp, Color.Gray, RoundedCornerShape(10.dp)) + .padding(vertical = 10.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.yanavi), + contentDescription = "yanavi" + ) + Spacer(modifier = Modifier.width(10.dp)) + Text(text = "Открыть в Яндекс Навигатор") + } + + } + + footerButton() + } + } + + if (isActionVisible) { + IsSubtaskCompletedAction(setStateAction = { isActionVisible = false }, subtask) + } + +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun IsSubtaskCompletedAction(setStateAction: (Boolean) -> Unit = { }, subtask: Subtasks) { + var currentStep by remember { + mutableStateOf(0) + } + var title by remember { + mutableStateOf("Вам удалось выполнить подзадачу?") + } + + val actionController = rememberNavController() + var isFullScreen by remember { + mutableStateOf(false) + } + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = false, + ) + + ModalBottomSheet( + onDismissRequest = { + setStateAction(false) + }, + + containerColor = Color.White, + modifier = Modifier.fillMaxHeight(), + sheetState = sheetState + ) { + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp), horizontalArrangement = Arrangement.Start + ) { + Text( + text = title, + fontSize = 20.sp, + textAlign = TextAlign.Center, + modifier = Modifier.weight(1f) + ) + } + Column { + NavHost(navController = actionController, startDestination = "initial") { + composable("initial") { + InitialStep(subtask = subtask, controller = actionController) + } + composable("success") { + SuccessStep(subtask = subtask, controller = actionController) + title = "Когда вы выполнили подзадачу?" + } + composable("failure") { + isFullScreen = true + LaunchedEffect(sheetState) { + sheetState.expand() + } + + title = "Что помешало выполнить подзадачу?" + FailureStep(subtask = subtask, controller = actionController) + } + } + } + + + } + + } +} + + +@Composable +fun InitialStep(subtask: Subtasks, controller: NavHostController) { + Column { + JDEButton(type = ButtonType.SUCCESS, onClick = { controller.navigate("success") }) { + Text(text = "Подзадача выполнена", fontSize = 15.sp, fontWeight = FontWeight.Bold) + } + Spacer(modifier = Modifier.height(10.dp)) + JDEButton(type = ButtonType.WARNING, onClick = {controller.navigate("failure")}) { + Text(text = "У меня возникла проблема", fontSize = 15.sp, fontWeight = FontWeight.Bold) + } + Spacer(modifier = Modifier.height(40.dp)) + } + +} + + +@Preview(showBackground = true) +@Composable +fun SuccessStep( + subtask: Subtasks = Subtasks(), + controller: NavHostController = rememberNavController() +) { + + var date by remember { + mutableStateOf("") + } + + var time by remember { + mutableStateOf("") + } + + Column { + Column( + Modifier + .border( + 2.dp, Color.Gray, RoundedCornerShape(10.dp) + ) + .fillMaxWidth() + .padding(horizontal = 15.dp, vertical = 15.dp) + ) { + Row( + Modifier + .fillMaxWidth() + .padding(bottom = 15.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(1f), + text = "${subtask.text}", + fontWeight = FontWeight.Bold, + fontSize = 20.sp + ) + Spacer(modifier = Modifier.width(10.dp)) + + IconButton(onClick = { /*TODO*/ }, modifier = Modifier.weight(0.1f)) { + Image( + painter = painterResource(id = R.drawable.tick_default), + contentDescription = "tick" + ) + } + } + Spacer(modifier = Modifier.height(10.dp)) + + Row(Modifier.fillMaxWidth()) { + TextInput(modifier = Modifier.weight(.66f), date, { date = it }, "Дата") + Spacer(modifier = Modifier.width(10.dp)) + TextInput(modifier = Modifier.weight(.33f), time, { date = time }, "Время") + } + + } + + Spacer(modifier = Modifier.height(20.dp)) + ActiveButton(onClick = { /*TODO*/ }, text = "Сохранить", modifier = Modifier.fillMaxWidth()) + } + + +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +@Composable +fun FailureStep( + subtask: Subtasks = Subtasks(), + controller: NavHostController = rememberNavController() +) { + + var failureDesk by remember { + mutableStateOf("") + } + var date by remember { + mutableStateOf("") + } + Column { + Text( + text = "Опишите причину, по которой вам не удалось выполнить подзадачу", + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + color = Color.Gray + ) + Spacer(modifier = Modifier.height(40.dp)) + TextField(value = failureDesk, + onValueChange = { failureDesk = it }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = "Введите текст") }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color(0xFFF2F2F2), + focusedContainerColor = Color(0xFFF2F2F2), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ) + ) + Spacer(modifier = Modifier.height(10.dp)) + Text(text = "Проблема возникла:", fontSize = 14.sp, fontWeight = FontWeight.Bold) + TextField( + value = date, + onValueChange = { date = it }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = "Дата") }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color(0xFFF2F2F2), + focusedContainerColor = Color(0xFFF2F2F2), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ), + trailingIcon = {Icon( + painter = painterResource(id = R.drawable.calendar_default), + contentDescription = "" + )} + ) + Spacer(modifier = Modifier.height(10.dp)) + + TextField( + value = date, + onValueChange = { date = it }, + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = "Дата") }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color(0xFFF2F2F2), + focusedContainerColor = Color(0xFFF2F2F2), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ), + trailingIcon = {Icon(painter = painterResource(id = R.drawable.calendar_default), + contentDescription = "" + )} + ) + Spacer(modifier = Modifier.height(20.dp)) + ActiveButton(onClick = { /*TODO*/ }, text = "Отправить", modifier = Modifier.fillMaxWidth()) + } +} + +@Preview(showBackground = true) +@Composable +fun TextInput( + modifier: Modifier = Modifier, + value: String = "24.12.2023", + onValueChange: (String) -> Unit = {}, + placeholder: String = "Дата" +) { + + var isFocused by remember { + mutableStateOf(true) + } + + + BasicTextField( + modifier = modifier + .onFocusEvent { + isFocused = it.isFocused + }, + value = value, + onValueChange = onValueChange, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + decorationBox = { + Box( + Modifier + .height(50.dp) + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFFF2F2F2)) + .padding(horizontal = 10.dp, vertical = 5.dp) + ) { + Text( + modifier = Modifier.align(if (isFocused) Alignment.TopStart else Alignment.CenterStart), + text = placeholder, + fontWeight = FontWeight.Normal, + fontSize = if (isFocused) 13.sp else 18.sp, + color = Color.Gray + ) + if (isFocused) { + Text( + modifier = Modifier.align(Alignment.BottomStart), + text = value, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + } + + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/components/TaskComponent.kt b/app/src/main/java/com/example/mpdriver/components/TaskComponent.kt new file mode 100644 index 0000000..22b2870 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/TaskComponent.kt @@ -0,0 +1,276 @@ +package com.example.mpdriver.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.KeyboardArrowUp +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.mpdriver.R +import com.example.mpdriver.api.Subtasks +import com.example.mpdriver.api.TaskResponse +import com.example.mpdriver.recievers.TimeTickReciever +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.UtcOffset +import kotlinx.datetime.asTimeZone +import kotlinx.datetime.format.byUnicodePattern +import kotlinx.datetime.toInstant +import kotlinx.datetime.until +import kotlin.math.abs + + +enum class TaskStatus { + DANGER, + SUCCESS, + WARNING, + DEFAULT +} + +@Preview(showBackground = true) +@Composable +fun Task(modifier: Modifier = Modifier, + children: @Composable() ()->Unit = {}, + taskResponse: TaskResponse = TaskResponse(1, "2024-06-01T00:00", "2024-06-10T00:00"), + footerButton: @Composable ()-> Unit = {}) { + var expanded by remember { + mutableStateOf(false) + } + + var nowTime by remember { + mutableStateOf(Clock.System.now()) + } + + TimeTickReciever.registerHandler { + nowTime = Clock.System.now() + } + + val startPln = LocalDateTime.parse(taskResponse.startPln!!) + val endPln = LocalDateTime.parse(taskResponse.endPln!!) + + val dateFormat = LocalDateTime.Format { + byUnicodePattern("d.MM.yyyy") + } + + val timeFormat = LocalDateTime.Format { + byUnicodePattern("HH:mm") + } + + val leastBase = nowTime.until( + endPln.toInstant(offset = UtcOffset(3)), + timeZone = UtcOffset(3).asTimeZone(), unit = DateTimeUnit.MINUTE) + + val isDelay = leastBase != abs(leastBase) + val leastDays = abs( leastBase) / 60 / 24 + val leastHours = abs(leastBase) / 60 - leastDays * 24 + val leastMinutes = abs(leastBase) - leastDays * 60 * 24 - leastHours * 60 + + + val status = when { + taskResponse.status == "Completed" && !isDelay -> TaskStatus.SUCCESS + taskResponse.status == "Completed" && isDelay -> TaskStatus.WARNING + taskResponse.status == "Cancelled" -> TaskStatus.WARNING + taskResponse.status == "InProgress" || taskResponse.status == "NotDefined" && isDelay -> TaskStatus.DANGER + else -> TaskStatus.DEFAULT + } + + + + Column( + modifier + .border( + 2.dp, when (status) { + TaskStatus.DEFAULT -> Color.Gray + TaskStatus.SUCCESS -> Color(0xFF45900B) + TaskStatus.DANGER -> Color(0xFFE5332A) + TaskStatus.WARNING -> Color(0xFFFFC700) + }, RoundedCornerShape(10.dp) + ) + .fillMaxWidth() + .padding(horizontal = 15.dp, vertical = 15.dp) + ) { + Row( + Modifier + .fillMaxWidth() + .padding(bottom = 15.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(1f), + text = "Движение по маршруту\n${taskResponse.route?.name}", + fontWeight = FontWeight.Bold, + fontSize = 20.sp + ) + Spacer(modifier = Modifier.width(10.dp)) + + IconButton(onClick = { /*TODO*/ }, modifier = Modifier.weight(0.1f)) { + Image( + painter = painterResource(id = R.drawable.tick_default), + contentDescription = "tick" + ) + } + } + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier.weight(1f), + mainText = "${taskResponse.route?.truck?.gost}", + subText = "Номер ТС" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier.weight(1f), + mainText = "${if (taskResponse.route!!.trailer != null) taskResponse.route!!.trailer!!.gost else "-"}", + subText = "Номер прицепа" + ) + } + InformationPlaceholderSmall( + Modifier + .fillMaxWidth() + .padding(bottom = 20.dp), + mainText = if (taskResponse.route!!.temperatureProperty == 1) "Горячая" else "Холодная", + subText = "Тип перевозки" + ) { + when (taskResponse.route!!.temperatureProperty) { + 1 -> Image(painter = painterResource(id = R.drawable.hottransport), contentDescription = "" ) + else -> Image(painter = painterResource(id = R.drawable.coltransport), contentDescription = "" ) + } + + } + children() + AnimatedVisibility(visible = expanded) { + Column { + InformationPlaceholderSmall( + Modifier + .fillMaxWidth() + .padding(bottom = 20.dp), + mainText = "${taskResponse.text}", + subText = "Полный маршрут" + ) + + Text( + modifier = Modifier.padding(bottom = 5.dp), + text = "Начало маршрута", + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier.weight(2f), + mainText = dateFormat.format(startPln), + subText = "Дата" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier.weight(1f), + mainText = timeFormat.format(startPln), + subText = "Время" + ) + } + + Text( + modifier = Modifier.padding(bottom = 5.dp), + text = "Конец маршрута", + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier.weight(2f), + mainText = dateFormat.format(endPln), + subText = "Дата" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier.weight(1f), + mainText = timeFormat.format(endPln), + subText = "Время" + ) + } + + Text( + modifier = Modifier.padding(bottom = 5.dp), + text = if (isDelay) "Задержка" else "Осталось времени до конца маршрута", + fontWeight = FontWeight.Bold, + fontSize = if (isDelay) 20.sp else 14.sp, + color = if (isDelay) Color(0xFFE5332A) else Color.Black + ) + Row(Modifier.padding(bottom = 20.dp)) { + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastDays.toString(), + subText = "Дни" + ) + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastHours.toString(), + subText = "Часы" + ) + + Spacer(modifier = Modifier.width(10.dp)) + InformationPlaceholderSmall( + Modifier + .weight(1f) + .border( + 1.dp, + color = if (isDelay) Color(0xFFE5332A) else Color.Gray, + shape = RoundedCornerShape(10.dp) + ), + mainText = leastMinutes.toString(), + subText = "Минуты" + ) + } + + } + } + Divider(thickness = 2.dp, color = Color.Gray) + TextButton(onClick = { expanded = !expanded }, colors = ButtonDefaults.buttonColors(contentColor = Color.Black, containerColor = Color.Transparent)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Text(text = if (expanded) "Скрыть" else "Развернуть", fontSize = 16.sp) + Icon(Icons.Rounded.KeyboardArrowUp, contentDescription = "Chevron") + } + } + footerButton() + } +} + + diff --git a/app/src/main/java/com/example/mpdriver/components/feed/ActiveTask.kt b/app/src/main/java/com/example/mpdriver/components/feed/ActiveTask.kt new file mode 100644 index 0000000..b2afcc7 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/feed/ActiveTask.kt @@ -0,0 +1,94 @@ +package com.example.mpdriver.components.feed + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Call +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.outlined.Clear +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import com.example.mpdriver.api.TaskResponse +import com.example.mpdriver.components.ButtonType +import com.example.mpdriver.components.EmptyList +import com.example.mpdriver.components.IteractionButton +import com.example.mpdriver.components.JDEButton +import com.example.mpdriver.components.Subtask +import com.example.mpdriver.components.Task + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ActiveTask(activeTask: TaskResponse? = null, hostController: NavHostController) { + + Column( + Modifier + .fillMaxWidth() + .padding(vertical = 15.dp) + ) { + if (activeTask == null) { + EmptyList(Modifier.padding(vertical = 60.dp), text = "У вас нет активных задач") + return + } + Text( + text = "Активная задача", + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, + fontSize = 18.sp + ) + + activeTask.let { + IteractionButton(onClick = { hostController.navigate("tasks/${it.id}") }) { + Task(taskResponse = it) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = "Активная подзадача", + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, + fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(10.dp)) + + + activeTask.let { task -> + IteractionButton(onClick = { /*TODO*/ }) { + Subtask(subtask = task.subtasks.filter { it.status == "InProgress" }[0]) + } + } + } + + +} + + diff --git a/app/src/main/java/com/example/mpdriver/components/feed/DataCard.kt b/app/src/main/java/com/example/mpdriver/components/feed/DataCard.kt new file mode 100644 index 0000000..501946e --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/components/feed/DataCard.kt @@ -0,0 +1,79 @@ +package com.example.mpdriver.components.feed + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import com.example.mpdriver.components.InformationPlaceholderBig + + +@Composable +fun FeedTaskDataCard( + modifier: Modifier = Modifier, + title: String = "Запланированные задачи", + count: Int = 2, + date: String = "30.11.2023", + dateDescription: String = "Ближайшая", + buttonLabel: String = "Смотреть запланированные задачи", + hostController: NavHostController, + link: String +) { + Column(modifier = modifier.fillMaxWidth()) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + InformationPlaceholderBig( + mainText = "$count", + subText = "Количество", + modifier = Modifier.weight(1f) + ) + Spacer(modifier = Modifier.weight(0.1f)) + InformationPlaceholderBig( + mainText = date, + subText = dateDescription, + modifier = Modifier.weight(1f) + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { hostController.navigate(link) }, + colors = ButtonDefaults.buttonColors( + contentColor = Color.White, + containerColor = Color.Black + ), + shape = RoundedCornerShape(10.dp), + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = buttonLabel, fontSize = 15.sp) + Spacer(modifier = Modifier.weight(0.1f)) + Icon(Icons.Filled.ArrowForward, contentDescription = "Arrow Forward") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/recievers/TimeTickReciever.kt b/app/src/main/java/com/example/mpdriver/recievers/TimeTickReciever.kt new file mode 100644 index 0000000..c4d6a11 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/recievers/TimeTickReciever.kt @@ -0,0 +1,31 @@ +package com.example.mpdriver.recievers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import kotlin.random.Random + + +class TimeTickReciever: BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == Intent.ACTION_TIME_TICK) { + handlers.forEach { + it.value() + } + } + } + + companion object { + var handlers = mutableMapOf Unit>() + fun registerHandler(cb: () -> Unit): Int { + handlers[cb.hashCode()] = cb + return cb.hashCode() + } + + fun unregisterHandler(hashCode: Int) { + handlers.remove(hashCode) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/screens/Feed.kt b/app/src/main/java/com/example/mpdriver/screens/Feed.kt new file mode 100644 index 0000000..9025cef --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/screens/Feed.kt @@ -0,0 +1,117 @@ +package com.example.mpdriver.screens + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.example.mpdriver.api.TaskApi +import com.example.mpdriver.api.TaskResponse +import com.example.mpdriver.components.Layout +import com.example.mpdriver.components.feed.ActiveTask +import com.example.mpdriver.components.feed.FeedTaskDataCard +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.format.byUnicodePattern + + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun Feed(modifier: Modifier = Modifier, hostController: NavHostController) { +// Fetch active task + + var isLoading by remember { + mutableStateOf(true) + } + + val context = LocalContext.current + var activeTask by remember { + mutableStateOf(null) + } + var plannedTasks by remember { + mutableStateOf>(emptyList()) + } + var completedTasks by remember { + mutableStateOf>(emptyList()) + } + + val taskApi = TaskApi(context) + + taskApi.getActiveTask { task -> + isLoading = false + task.id?.let { + activeTask = task + } + } + + taskApi.getPlannedTasks { tasks -> + plannedTasks = tasks + } + val dateFormat = LocalDateTime.Format { + byUnicodePattern("dd.MM.yyyy") + } + + val dataList = listOf( + mapOf( + "title" to "Запланированные задачи", + "count" to plannedTasks.count(), + "date" to when (plannedTasks.count()) { + 0 -> "-" + else -> dateFormat.format(LocalDateTime.parse(plannedTasks[0].startPln!!)) + }, + "buttonLabel" to "Смотреть запланированные задачи", + "dateDescription" to "Ближайшая", + ), + mapOf( + "title" to "Завершенные задачи", + "count" to completedTasks.count(), + "date" to when (completedTasks.count()) { + 0 -> "-" + else -> dateFormat.format(LocalDateTime.parse(completedTasks[0].startPln!!)) + }, + "buttonLabel" to "Смотреть завершенные задачи", + "dateDescription" to "Последняя", + ), + ) + + if(isLoading) { + Column (Modifier.fillMaxWidth().padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator(color = Color(0xFFE5332A)) + } + return + } + + Layout(dataList = dataList, header = { + Column { + ActiveTask(activeTask = activeTask, hostController = hostController) + Spacer(modifier = Modifier.height(20.dp)) + } + + }) { + FeedTaskDataCard( + title = it["title"].toString(), + count = it["count"].toString().toInt(), + dateDescription = it["dateDescription"].toString(), + buttonLabel = it["buttonLabel"].toString(), + hostController = hostController, link = "feed/planned-tasks", + date = it["date"].toString() + ) + } +} + + + + diff --git a/app/src/main/java/com/example/mpdriver/screens/Map.kt b/app/src/main/java/com/example/mpdriver/screens/Map.kt new file mode 100644 index 0000000..874f4f1 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/screens/Map.kt @@ -0,0 +1,55 @@ +package com.example.mpdriver.screens +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import com.example.mpdriver.components.ComposableLifecycle +import com.yandex.mapkit.MapKitFactory +import com.yandex.mapkit.geometry.Point +import com.yandex.mapkit.map.CameraPosition +import com.yandex.mapkit.mapview.MapView as YaMapView + + + +@Composable +fun MapScreen() { + var map: YaMapView = YaMapView(LocalContext.current) + val ctx = LocalContext.current + + ComposableLifecycle { _, event -> + when(event) { + Lifecycle.Event.ON_START -> { + MapKitFactory.getInstance().onStart() + println("MAP STARTED") + map.onStart() + } + Lifecycle.Event.ON_STOP -> { + MapKitFactory.getInstance().onStop() + println("MAP STOPPED") + map.onStop() + } + Lifecycle.Event.ON_CREATE -> { + MapKitFactory.initialize(ctx) + println("MAP CREATED") + } + else -> {} + } + + } + AndroidView( + modifier = Modifier.fillMaxWidth(), + factory = { _ -> + map.map.move(CameraPosition(Point(55.777586, 37.737731), 18.0f, 0.0f, 0.0f)) + return@AndroidView map + } + + ) +} + diff --git a/app/src/main/java/com/example/mpdriver/screens/PhoneCodeInputScreen.kt b/app/src/main/java/com/example/mpdriver/screens/PhoneCodeInputScreen.kt new file mode 100644 index 0000000..dcac261 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/screens/PhoneCodeInputScreen.kt @@ -0,0 +1,132 @@ +package com.example.mpdriver.screens + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import com.example.mpdriver.api.Auth +import com.example.mpdriver.storage.Database +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + + +@Composable +fun PhoneCodeInputScreen(navHostController: NavHostController) { + val context = LocalContext.current + var errorText by remember { + mutableStateOf("") + } + var code by remember { + mutableStateOf("") + } + var isSuccess by remember { + mutableStateOf(false) + } + + Column( + Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.Center + ) { + + Text( + modifier = Modifier.fillMaxWidth(), + text = "JDE Перевозчик", + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(15.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = "Введите код из СМС", + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + color = Color.Gray + ) + Spacer(modifier = Modifier.height(15.dp)) + + BasicTextField( + value = code, + onValueChange = { + if (it.length > 4) { + code = it.slice(0..3) + return@BasicTextField + } + code = it + if (code.length == 4) { + Auth(context).getToken(phone = Database.phoneNumber!!, code = code) { + MainScope().launch { + it.accessToken?.let { + isSuccess = true + errorText = "" + navHostController.navigate("home") + return@launch + } + + errorText = "Неверный OTP код." + } + } + } + }, + + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + decorationBox = { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + repeat(4) { index -> + val isFocused = code.length == index + val char = when { + index >= code.length -> "" + else -> code[index].toString() + } + Text( + modifier = Modifier + .padding(horizontal = 10.dp) + .width(40.dp) + .border( + if (isFocused) 3.dp else 1.dp, + color = when { + isSuccess -> Color(0xFF45900B) + errorText != "" -> Color.Red + else -> Color.Gray + }, + RoundedCornerShape(10.dp) + ) + .padding(10.dp), + text = char, + textAlign = TextAlign.Center, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } + }) + Spacer(modifier = Modifier.padding(bottom = 10.dp)) + Text(text = errorText, color = Color(0xFFE5332A), fontWeight = FontWeight.Bold) + } +} diff --git a/app/src/main/java/com/example/mpdriver/screens/PhoneInputScreen.kt b/app/src/main/java/com/example/mpdriver/screens/PhoneInputScreen.kt new file mode 100644 index 0000000..7e0f2da --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/screens/PhoneInputScreen.kt @@ -0,0 +1,161 @@ +package com.example.mpdriver.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import com.example.mpdriver.NotificationService +import com.example.mpdriver.api.Auth +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + + +@Composable +fun PhoneInputScreen(navHostController: NavHostController) { + val context = LocalContext.current + var errorText by remember { + mutableStateOf("") + } + var phone by remember { + mutableStateOf("") + } + + Column( + Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.Center + ) { + + Text( + modifier = Modifier.fillMaxWidth(), + text = "JDE Перевозчик", + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(15.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = "Введите номер телефона", + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + color = Color.Gray + ) + Spacer(modifier = Modifier.height(15.dp)) + TextField( + isError = errorText != "", + modifier = Modifier.fillMaxWidth(), + value = phone, onValueChange = { phone = it.filter { it.isDigit() } }, + placeholder = { Text(text = "+7 (999) 999-99-99") }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color(0xFFF2F2F2), + focusedContainerColor = Color(0xFFE2E2E2), + focusedIndicatorColor = Color(0xFFE5332A), + errorContainerColor = Color(0xFFE2E2E2), + errorTextColor = Color(0xFFE5332A) + ), + visualTransformation = VisualTransformation { text -> + TransformedText( + AnnotatedString( + phoneChecking(text.text) + ), object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + when (offset) { + 1 -> return offset + 1 + in 2..4 -> return offset + 3 + in 5..7 -> return offset + 5 + in 8..9 -> return offset + 6 + in 10..11 -> return offset + 7 + else -> return offset + } + } + + override fun transformedToOriginal(offset: Int): Int { + return offset + } + + } + ) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone, + autoCorrect = false, + imeAction = ImeAction.Go + ), + keyboardActions = KeyboardActions(onGo = { + Auth(context).getPhoneCode(phone.filter { it.isDigit() }) { + + it.code?.let { + NotificationService(context).showNotificationAuthCode(it.toString()) + + MainScope().launch { + navHostController.navigate("auth/code") + } + + } + + it.error?.let { error -> + errorText = it.langs!!.ru!! + } + + + } + }) + ) + Text(text = errorText, color = Color(0xFFE5332A), fontWeight = FontWeight.Bold) + } +} + +fun phoneChecking(text: String): String { + val digitText = text.filter { char -> char.isDigit() } + + + when (digitText.length) { + 1 -> return "+7" + in 2..4 -> return "+7 (${digitText.slice(1.. return "+7 (${digitText.slice(1..3)}) ${digitText.slice(4.. return "+7 (${digitText.slice(1..3)}) ${digitText.slice(4..6)}-${ + digitText.slice( + 7.. return "+7 (${digitText.slice(1..3)}) ${digitText.slice(4..6)}-${ + digitText.slice( + 7..8 + ) + }-${digitText.slice(9..>(mutableListOf()) + } + var activeTab by remember { + mutableStateOf(0) + } + + val listState = rememberLazyListState() + var offset = listState.firstVisibleItemScrollOffset + listState.firstVisibleItemIndex + + val tabsData = listOf(HeaderTabsData(0, "Подзадачи"), HeaderTabsData(1, "События")) + + ComposableLifecycle { _, event -> + when (event) { + Lifecycle.Event.ON_CREATE -> { + api.getSubtasks(taskId) { + datalist = it + isLoading = false + } + } + else -> {} + } + } + + if(isLoading) { + Column ( + Modifier + .fillMaxWidth() + .padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator(color = Color(0xFFE5332A)) + } + return + } + + Layout(state = listState, dataList = datalist, header = { + HeaderTabs(modifier = Modifier.onGloballyPositioned { lc -> + val cords = lc.boundsInParent() + floatingActionShowOffset = cords.bottom + }, tabsData = tabsData, activeTab = activeTab) { + activeTab = it + } + }) { + Subtask(subtask = it) + } + + AnimatedVisibility(visible = offset + 200 > floatingActionShowOffset + 100, enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut()) { + ExtendedFloatingActionButton(onClick = { /*TODO*/ }, + shape = RoundedCornerShape(10.dp), containerColor = Color.White, contentColor = Color.Black) { + Row(Modifier.fillMaxWidth()) { + Text(text = "Скрыть завершенные задачи") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/screens/TasksList.kt b/app/src/main/java/com/example/mpdriver/screens/TasksList.kt new file mode 100644 index 0000000..dd2730d --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/screens/TasksList.kt @@ -0,0 +1,109 @@ +package com.example.mpdriver.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.example.mpdriver.api.TaskApi +import com.example.mpdriver.api.TaskResponse +import com.example.mpdriver.components.ActiveButton +import com.example.mpdriver.components.HeaderTabs +import com.example.mpdriver.components.HeaderTabsData +import com.example.mpdriver.components.Layout +import com.example.mpdriver.components.StaleButton +import com.example.mpdriver.components.Task +import com.example.mpdriver.storage.Database +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + + +@Composable +fun TasksList(modifier: Modifier = Modifier, hostController: NavHostController) { + val api = TaskApi(LocalContext.current) + + var isLoading by remember { + mutableStateOf(true) + } + + + var itemsList by remember { + mutableStateOf>(emptyList()) + } + api.getPlannedTasks { + itemsList = it + isLoading = false + } + + var activeTab by remember { + mutableIntStateOf(0) + } + + + if(isLoading) { + Column (Modifier.fillMaxWidth().padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator(color = Color(0xFFE5332A)) + } + return + } + Layout(dataList = itemsList, header = { + HeaderTabs( + tabsData = listOf( + HeaderTabsData(0, "Запланированные"), + HeaderTabsData(1, "Завершенные") + ), activeTab = activeTab + ) { + activeTab = it + } + }) { + Task(Modifier.padding(bottom = 10.dp), taskResponse = it) { + when (activeTab) { + 0 -> Button( + onClick = { + api.setTaskStatusInProgress(it.id!!) { + MainScope().launch { + hostController.navigate("feed") + } + } + }, + colors = ButtonDefaults.buttonColors( + contentColor = Color.White, + containerColor = Color.Black, + disabledContentColor = Color.White, + disabledContainerColor = Color.Gray + ), + shape = RoundedCornerShape(10.dp), + enabled = if (Database.tasks.filter { it.status == "InProgress" }.count() >= 1) false else true, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Приступить к выполнению") + } + } + } + } + +} + diff --git a/app/src/main/java/com/example/mpdriver/storage/Base.kt b/app/src/main/java/com/example/mpdriver/storage/Base.kt new file mode 100644 index 0000000..650d8aa --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/storage/Base.kt @@ -0,0 +1,55 @@ +package com.example.mpdriver.storage + +import com.example.mpdriver.api.TaskResponse +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.tencent.mmkv.MMKV +import java.lang.reflect.Type + +object Database { + val kv = MMKV.defaultMMKV() + + var tasks: List + get() { + val data = kv.decodeString("tasks") + data?.let { json -> + val type: Type = object : TypeToken>() {}.type + return Gson().fromJson(json, type) + } + return emptyList() + } + set(value) { + val data = Gson().toJson(value) + kv.encode("tasks", data) + } + + var planned_tasks: List + get() { + val data = kv.decodeString("planned_tasks") + data?.let { json -> + val type: Type = object : TypeToken>() {}.type + return Gson().fromJson(json, type) + } + return emptyList() + } + set(value) { + val data = Gson().toJson(value) + println(value) + println(data) + kv.encode("planned_tasks", data) + } + + var access_token: String? + get() { + return kv.decodeString("access_token") + } + set(value) { + kv.encode("access_token", value) + } + + var phoneNumber: String? + get() = kv.decodeString("phone_number") + set(value) { + kv.encode("phone_number", value) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/ui/theme/Color.kt b/app/src/main/java/com/example/mpdriver/ui/theme/Color.kt new file mode 100644 index 0000000..8243bc4 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.mpdriver.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/ui/theme/Theme.kt b/app/src/main/java/com/example/mpdriver/ui/theme/Theme.kt new file mode 100644 index 0000000..5eb9125 --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.mpdriver.ui.theme + +import android.app.Activity +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 MPDriverTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mpdriver/ui/theme/Type.kt b/app/src/main/java/com/example/mpdriver/ui/theme/Type.kt new file mode 100644 index 0000000..2e238cf --- /dev/null +++ b/app/src/main/java/com/example/mpdriver/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.mpdriver.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/bell.xml b/app/src/main/res/drawable/bell.xml new file mode 100644 index 0000000..aab5eb0 --- /dev/null +++ b/app/src/main/res/drawable/bell.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/bell_default.xml b/app/src/main/res/drawable/bell_default.xml new file mode 100644 index 0000000..02b4ee9 --- /dev/null +++ b/app/src/main/res/drawable/bell_default.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/calendar.xml b/app/src/main/res/drawable/calendar.xml new file mode 100644 index 0000000..4fabf7d --- /dev/null +++ b/app/src/main/res/drawable/calendar.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/calendar_default.xml b/app/src/main/res/drawable/calendar_default.xml new file mode 100644 index 0000000..5b18c8b --- /dev/null +++ b/app/src/main/res/drawable/calendar_default.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/chat.xml b/app/src/main/res/drawable/chat.xml new file mode 100644 index 0000000..a31e5a2 --- /dev/null +++ b/app/src/main/res/drawable/chat.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/chat_default.xml b/app/src/main/res/drawable/chat_default.xml new file mode 100644 index 0000000..e1eea04 --- /dev/null +++ b/app/src/main/res/drawable/chat_default.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/coltransport.xml b/app/src/main/res/drawable/coltransport.xml new file mode 100644 index 0000000..60b290c --- /dev/null +++ b/app/src/main/res/drawable/coltransport.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/home.xml b/app/src/main/res/drawable/home.xml new file mode 100644 index 0000000..eabcc1d --- /dev/null +++ b/app/src/main/res/drawable/home.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/home_default.xml b/app/src/main/res/drawable/home_default.xml new file mode 100644 index 0000000..4390415 --- /dev/null +++ b/app/src/main/res/drawable/home_default.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/hottransport.xml b/app/src/main/res/drawable/hottransport.xml new file mode 100644 index 0000000..c528692 --- /dev/null +++ b/app/src/main/res/drawable/hottransport.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/location.xml b/app/src/main/res/drawable/location.xml new file mode 100644 index 0000000..a2f5cf3 --- /dev/null +++ b/app/src/main/res/drawable/location.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/location_default.xml b/app/src/main/res/drawable/location_default.xml new file mode 100644 index 0000000..5a7996b --- /dev/null +++ b/app/src/main/res/drawable/location_default.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tick_default.xml b/app/src/main/res/drawable/tick_default.xml new file mode 100644 index 0000000..348fa4c --- /dev/null +++ b/app/src/main/res/drawable/tick_default.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/yanavi.xml b/app/src/main/res/drawable/yanavi.xml new file mode 100644 index 0000000..43f45d7 --- /dev/null +++ b/app/src/main/res/drawable/yanavi.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/zoom_icon.xml b/app/src/main/res/drawable/zoom_icon.xml new file mode 100644 index 0000000..10fd1ad --- /dev/null +++ b/app/src/main/res/drawable/zoom_icon.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/map_layout.xml b/app/src/main/res/layout/map_layout.xml new file mode 100644 index 0000000..cfd6e2d --- /dev/null +++ b/app/src/main/res/layout/map_layout.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..e02be66 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + MPDriver + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..c693e1e --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +