add offline mode

main
Ernest Litvinenko 2024-07-10 19:08:36 +03:00
parent d31bce2f89
commit 0074e651d0
25 changed files with 1623 additions and 450 deletions

View File

@ -10,6 +10,14 @@
</SelectionState> </SelectionState>
<SelectionState runConfigName="MainActivity"> <SelectionState runConfigName="MainActivity">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-07-09T21:11:01.409507Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/ernestlitvinenko/.android/avd/Small_Phone_API_28.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View File

@ -1,7 +1,9 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
id("com.google.gms.google-services")
alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.kotlinx.serialization)
id("com.apollographql.apollo3").version("3.8.3")
} }
android { android {
@ -50,8 +52,13 @@ android {
} }
} }
dependencies { apollo {
service("service") {
packageName.set("com.example.mpdriver")
}
}
dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
@ -76,4 +83,8 @@ dependencies {
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(libs.yandex.maps) implementation(libs.yandex.maps)
implementation(libs.kotlin.coroutines) implementation(libs.kotlin.coroutines)
implementation(platform("com.google.firebase:firebase-bom:33.1.1"))
implementation("com.apollographql.apollo3:apollo-runtime:3.8.3")
implementation("ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0")
} }

View File

@ -0,0 +1,58 @@
query fetchApplicationData ($userID: String!) {
tasks(userId: $userID) {
id
startPln
endPln
startFact
endFact
status
taskType
text
events {
id
}
subtasks {
id
}
route {
temperatureProperty
name
trailer {
gost
}
truck {
gost
}
}
}
subtasks(userId: $userID) {
id
startPln
endPln
startFact
endFact
status
taskType
text
station {
name
location {
lat
lon
}
}
}
notes(userId: $userID) {
id
taskId
noteStatus
tip
text
}
}

View File

@ -0,0 +1,7 @@
query GetActiveSubtaskID($userID: String!) {
task(userId: $userID, isActive: true) {
activeSubtask {
id
}
}
}

View File

@ -0,0 +1,15 @@
query GetActiveTaskId($userID: String!) {
task(isActive: true, userId: $userID) {
id
}
completedTasks: tasks(userId: $userID, isCompleted: true) {
id
startPln
}
plannedTasks: tasks(userId: $userID, isPlanned: true) {
id,
startPln
}
}

View File

@ -0,0 +1,8 @@
query GetPlannedTasksIDs($userID: String!) {
tasks(userId: $userID, isPlanned:true) {
id
subtasks {
id
}
}
}

View File

@ -0,0 +1,9 @@
query GetSubtaskByID($userID: String!, $subtaskID: String!) {
subtask(userId: $userID, subtaskId: $subtaskID) {
id
text
startPln
endPln
status
}
}

View File

@ -0,0 +1,20 @@
query GetTaskById($taskID: String!, $userID: String!) {
task(userId: $userID, taskId: $taskID) {
status
route {
name
temperatureProperty
truck {
gost
}
trailer {
gost
}
}
text
startPln
endPln
startFact
endFact
}
}

View File

@ -0,0 +1,7 @@
query GetTasksFeedScreen($userID: String!){
tasks(userId: $userID) {
id
text
}
}

View File

@ -0,0 +1,453 @@
type Query {
tasks(userId: String!, isPlanned: Boolean = false, isCompleted: Boolean = false): [AppTaskQL!]!
task(userId: String!, taskId: String = null, isActive: Boolean = null): AppTaskQL
countPlannedTasks(userId: String!): Int!
countCompletedTasks(userId: String!): Int!
notes(userId: String!): [AppNoteQL!]!
subtask(userId: String!, subtaskId: String!): SubtaskQL
subtasks(userId: String!): [SubtaskQL!]!
}
type AppTaskQL {
activeSubtask: SubtaskQL
id: String!
profileId: Int!
startPln: DateTime!
endPln: DateTime!
startFact: DateTime
endFact: DateTime
status: StatusEnumQl!
taskType: String!
text: String!
events: [AppEventQL!]!
subtasks: [SubtaskQL!]!
route: AppRouteQL!
}
type SubtaskQL {
id: String!
startPln: DateTime!
endPln: DateTime!
startFact: DateTime
endFact: DateTime
status: StatusEnumQl!
taskType: String!
text: String!
station: MSTQL
}
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""
scalar String
"""
Date with time (isoformat)
"""
scalar DateTime
enum StatusEnumQl {
CANCELLED
IN_PROGRESS
COMPLETED
NOT_DEFINED
}
type MSTQL {
name: String!
location: LocationQL!
}
type LocationQL {
lat: Float!
lon: Float!
}
"""
The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
"""
scalar Float
"""
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
"""
scalar Int
type AppEventQL {
id: String!
type: String!
text: String!
eventDatetime: DateTime!
}
type AppRouteQL {
temperatureProperty: MarshTemperaturePropertyQL!
name: String!
trailer: TRSQL
truck: TRSQL
}
enum MarshTemperaturePropertyQL {
HOT
COLD
UNDEFINED
}
type TRSQL {
gost: String
}
"""
The `Boolean` scalar type represents `true` or `false`.
"""
scalar Boolean
type AppNoteQL {
id: String!
userId: String!
taskId: String!
noteStatus: Int!
tip: Int!
text: String!
}
"""
A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
"""
type __Schema {
description: String
"""
A list of all types supported by this server.
"""
types: [__Type!]!
"""
The type that query operations will be rooted at.
"""
queryType: __Type!
"""
If this server supports mutation, the type that mutation operations will be rooted at.
"""
mutationType: __Type
"""
If this server support subscription, the type that subscription operations will be rooted at.
"""
subscriptionType: __Type
"""
A list of all directives supported by this server.
"""
directives: [__Directive!]!
}
"""
The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.
Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
"""
type __Type {
kind: __TypeKind!
name: String
description: String
specifiedByURL: String
fields(includeDeprecated: Boolean = false): [__Field!]
interfaces: [__Type!]
possibleTypes: [__Type!]
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
inputFields(includeDeprecated: Boolean = false): [__InputValue!]
ofType: __Type
isOneOf: Boolean
}
"""
An enum describing what kind of type a given `__Type` is.
"""
enum __TypeKind {
"""
Indicates this type is a scalar.
"""
SCALAR
"""
Indicates this type is an object. `fields` and `interfaces` are valid fields.
"""
OBJECT
"""
Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.
"""
INTERFACE
"""
Indicates this type is a union. `possibleTypes` is a valid field.
"""
UNION
"""
Indicates this type is an enum. `enumValues` is a valid field.
"""
ENUM
"""
Indicates this type is an input object. `inputFields` is a valid field.
"""
INPUT_OBJECT
"""
Indicates this type is a list. `ofType` is a valid field.
"""
LIST
"""
Indicates this type is a non-null. `ofType` is a valid field.
"""
NON_NULL
}
"""
Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.
"""
type __Field {
name: String!
description: String
args(includeDeprecated: Boolean = false): [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}
"""
Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.
"""
type __InputValue {
name: String!
description: String
type: __Type!
"""
A GraphQL-formatted string representing the default value for this input value.
"""
defaultValue: String
isDeprecated: Boolean!
deprecationReason: String
}
"""
One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.
"""
type __EnumValue {
name: String!
description: String
isDeprecated: Boolean!
deprecationReason: String
}
"""
A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.
"""
type __Directive {
name: String!
description: String
isRepeatable: Boolean!
locations: [__DirectiveLocation!]!
args(includeDeprecated: Boolean = false): [__InputValue!]!
}
"""
A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.
"""
enum __DirectiveLocation {
"""
Location adjacent to a query operation.
"""
QUERY
"""
Location adjacent to a mutation operation.
"""
MUTATION
"""
Location adjacent to a subscription operation.
"""
SUBSCRIPTION
"""
Location adjacent to a field.
"""
FIELD
"""
Location adjacent to a fragment definition.
"""
FRAGMENT_DEFINITION
"""
Location adjacent to a fragment spread.
"""
FRAGMENT_SPREAD
"""
Location adjacent to an inline fragment.
"""
INLINE_FRAGMENT
"""
Location adjacent to a variable definition.
"""
VARIABLE_DEFINITION
"""
Location adjacent to a schema definition.
"""
SCHEMA
"""
Location adjacent to a scalar definition.
"""
SCALAR
"""
Location adjacent to an object type definition.
"""
OBJECT
"""
Location adjacent to a field definition.
"""
FIELD_DEFINITION
"""
Location adjacent to an argument definition.
"""
ARGUMENT_DEFINITION
"""
Location adjacent to an interface definition.
"""
INTERFACE
"""
Location adjacent to a union definition.
"""
UNION
"""
Location adjacent to an enum definition.
"""
ENUM
"""
Location adjacent to an enum value definition.
"""
ENUM_VALUE
"""
Location adjacent to an input object type definition.
"""
INPUT_OBJECT
"""
Location adjacent to an input object field definition.
"""
INPUT_FIELD_DEFINITION
}
"""
Directs the executor to include this field or fragment only when the `if` argument is true.
"""
directive @include ("Included when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT
"""
Directs the executor to skip this field or fragment when the `if` argument is true.
"""
directive @skip ("Skipped when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT
"""
Marks an element of a GraphQL schema as no longer supported.
"""
directive @deprecated ("Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https:\/\/commonmark.org\/)." reason: String = "No longer supported") on FIELD_DEFINITION|ARGUMENT_DEFINITION|INPUT_FIELD_DEFINITION|ENUM_VALUE
"""
Exposes a URL that specifies the behaviour of this scalar.
"""
directive @specifiedBy ("The URL that specifies the behaviour of this scalar." url: String!) on SCALAR
schema {
query: Query
}

View File

@ -3,6 +3,7 @@ package com.example.mpdriver
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@ -22,6 +23,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -30,6 +32,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.sharp.Settings import androidx.compose.material.icons.sharp.Settings
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -37,9 +40,11 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -49,6 +54,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@ -58,9 +64,16 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.example.mpdriver.api.Api import com.example.mpdriver.api.Api
import com.example.mpdriver.api.TaskApi
import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.api.fetchAppDataToDB
import com.example.mpdriver.api.toJson
import com.example.mpdriver.recievers.TimeTickReciever import com.example.mpdriver.recievers.TimeTickReciever
import com.example.mpdriver.screens.Feed import com.example.mpdriver.screens.Feed
import com.example.mpdriver.screens.MapScreen import com.example.mpdriver.screens.MapScreen
import com.example.mpdriver.screens.NoteScreen
import com.example.mpdriver.screens.PhoneCodeInputScreen import com.example.mpdriver.screens.PhoneCodeInputScreen
import com.example.mpdriver.screens.PhoneInputScreen import com.example.mpdriver.screens.PhoneInputScreen
import com.example.mpdriver.screens.SubtaskScreen import com.example.mpdriver.screens.SubtaskScreen
@ -68,6 +81,8 @@ import com.example.mpdriver.screens.TasksList
import com.example.mpdriver.storage.Database import com.example.mpdriver.storage.Database
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.yandex.mapkit.MapKitFactory import com.yandex.mapkit.MapKitFactory
import kotlinx.coroutines.launch
import okhttp3.Dispatcher
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -79,6 +94,8 @@ class MainActivity : ComponentActivity() {
MapKitFactory.setApiKey("f4385b18-0740-454a-a71f-d20da7e8fc3b") MapKitFactory.setApiKey("f4385b18-0740-454a-a71f-d20da7e8fc3b")
MapKitFactory.initialize(this) MapKitFactory.initialize(this)
val rootDir = MMKV.initialize(this) val rootDir = MMKV.initialize(this)
Log.d("MMKV.KEYS", Database.allKeys!!.joinToString())
println("mmkv root: $rootDir") println("mmkv root: $rootDir")
registerReceiver(timeTickReciever, IntentFilter(Intent.ACTION_TIME_TICK)) registerReceiver(timeTickReciever, IntentFilter(Intent.ACTION_TIME_TICK))
@ -123,7 +140,32 @@ fun MainNavigator() {
mutableStateOf(false) mutableStateOf(false)
} }
var loadingData by remember {
mutableStateOf(true)
}
val bottomNavController = rememberNavController() val bottomNavController = rememberNavController()
val api = TaskApi(LocalContext.current)
LaunchedEffect(Unit) {
api.send_task_status_chains(onResponse = {
Database.dropUpdates()
})
apolloClient.fetchAppDataToDB()
loadingData = false
}
if (loadingData)
return Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(color = Color(0xFFE5332A))
Text( text = "Происходит обновление даннных, Пожалуйста, подождите.", fontSize = 16.sp, textAlign = TextAlign.Center)
}
}
Scaffold( Scaffold(
topBar = { topBar = {
Header( Header(
@ -132,9 +174,11 @@ fun MainNavigator() {
backLink = backLink backLink = backLink
) )
}, },
bottomBar = { Footer( bottomBar = {
Footer(
hostController = bottomNavController hostController = bottomNavController
) }, )
},
) { ) {
@ -145,14 +189,19 @@ fun MainNavigator() {
) { ) {
composable("feed", composable("feed",
exitTransition = { exitTransition = {
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Left,
tween(200)
)
} }
) { ) {
backLink = false backLink = false
headerTitle = "Лента" headerTitle = "Лента"
Box(modifier = Modifier Box(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight()) { .fillMaxHeight()
) {
Feed(hostController = bottomNavController) Feed(hostController = bottomNavController)
} }
} }
@ -160,15 +209,18 @@ fun MainNavigator() {
enterTransition = { enterTransition = {
slideIntoContainer( slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) AnimatedContentTransitionScope.SlideDirection.Left, tween(200)
)
} }
) { ) {
backLink = true backLink = true
headerTitle = "Задачи" headerTitle = "Задачи"
Box(modifier = Modifier Box(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight()) { .fillMaxHeight()
) {
TasksList(hostController = bottomNavController) TasksList(hostController = bottomNavController)
} }
} }
@ -176,7 +228,11 @@ fun MainNavigator() {
backLink = false backLink = false
headerTitle = "События" headerTitle = "События"
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Text(text = "События") Text(text = "События")
} }
@ -184,21 +240,33 @@ fun MainNavigator() {
composable("settings") { composable("settings") {
backLink = false backLink = false
headerTitle = "Настройки" headerTitle = "Настройки"
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Text(text = "Profile") Text(text = "Profile")
} }
} }
composable("notifications") { composable("notifications") {
backLink = false backLink = false
headerTitle = "Уведомления" headerTitle = "Уведомления"
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { Box(
Text(text = "Notifications") modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
NoteScreen()
} }
} }
composable("chat") { composable("chat") {
backLink = false backLink = false
headerTitle = "Чат" headerTitle = "Чат"
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight()) { Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Text(text = "Chat") Text(text = "Chat")
} }
} }
@ -218,15 +286,18 @@ fun MainNavigator() {
}), }),
enterTransition = { enterTransition = {
slideIntoContainer( slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Left, tween(200)) AnimatedContentTransitionScope.SlideDirection.Left, tween(200)
)
} }
) { bse -> ) { bse ->
bse.arguments?.let { args -> bse.arguments?.let { args ->
headerTitle = "Детали задачи" headerTitle = "Детали задачи"
backLink = true backLink = true
Box(modifier = Modifier Box(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight()){ .fillMaxHeight()
) {
SubtaskScreen(args.getLong("taskId")) SubtaskScreen(args.getLong("taskId"))
} }
return@composable return@composable
@ -300,14 +371,19 @@ data class FooterNavMenuLabel(
) )
@Composable @Composable
fun Footer(modifier: Modifier = Modifier, fun Footer(
modifier: Modifier = Modifier,
hostController: NavHostController hostController: NavHostController
) { ) {
val menuItems = mapOf<String, FooterNavMenuLabel>( val menuItems = mapOf<String, FooterNavMenuLabel>(
"feed" to FooterNavMenuLabel("Лента", R.drawable.home_default, R.drawable.home), "feed" to FooterNavMenuLabel("Лента", R.drawable.home_default, R.drawable.home),
"events" to FooterNavMenuLabel("События", R.drawable.calendar_default, R.drawable.calendar), "events" to FooterNavMenuLabel("События", R.drawable.calendar_default, R.drawable.calendar),
"notifications" to FooterNavMenuLabel("Уведомления", R.drawable.bell_default, R.drawable.bell), "notifications" to FooterNavMenuLabel(
"Уведомления",
R.drawable.bell_default,
R.drawable.bell
),
"chat" to FooterNavMenuLabel("Чат", R.drawable.chat_default, R.drawable.chat), "chat" to FooterNavMenuLabel("Чат", R.drawable.chat_default, R.drawable.chat),
"map" to FooterNavMenuLabel("Карта", R.drawable.location_default, R.drawable.location) "map" to FooterNavMenuLabel("Карта", R.drawable.location_default, R.drawable.location)
) )
@ -344,10 +420,16 @@ fun Footer(modifier: Modifier = Modifier,
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (activeRoute == it.key) { if (activeRoute == it.key) {
Image(painter = painterResource(id = it.value.imageActive), contentDescription = "") Image(
painter = painterResource(id = it.value.imageActive),
contentDescription = ""
)
Text(text = it.value.title, fontSize = 11.sp, color = Color.Black) Text(text = it.value.title, fontSize = 11.sp, color = Color.Black)
} else { } else {
Image(painter = painterResource(id = it.value.defaultImage), contentDescription = "") Image(
painter = painterResource(id = it.value.defaultImage),
contentDescription = ""
)
Text(text = it.value.title, fontSize = 11.sp, color = Color.Gray) Text(text = it.value.title, fontSize = 11.sp, color = Color.Gray)
} }

View File

@ -18,6 +18,7 @@ import okhttp3.Response
import okhttp3.internal.EMPTY_REQUEST import okhttp3.internal.EMPTY_REQUEST
import java.io.IOException import java.io.IOException
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.concurrent.TimeUnit
data class Langs( data class Langs(
@ -41,11 +42,10 @@ inline fun <reified T> Response.parseList(): List<T> {
open class Api(val ctx: Context) { open class Api(val ctx: Context) {
val kv = MMKV.defaultMMKV()
val clientCheckAuth = OkHttpClient.Builder().build() val clientCheckAuth = OkHttpClient.Builder().build()
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).writeTimeout(30 ,TimeUnit.SECONDS)
.addInterceptor { chain -> .addInterceptor { chain ->
val req = chain.request().newBuilder() val req = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${Database.access_token}") .addHeader("Authorization", "Bearer ${Database.access_token}")
@ -55,7 +55,7 @@ open class Api(val ctx: Context) {
.build() .build()
val BASE_URL = "http://147.45.107.119:8000/api/v1" val BASE_URL = "http://192.168.0.101:8000/api/v1"
fun performRequest(clientReq: Request, errorHandler: (Exception) -> Unit = {}, handler: (Response) -> Unit) { fun performRequest(clientReq: Request, errorHandler: (Exception) -> Unit = {}, handler: (Response) -> Unit) {
client.newCall(clientReq).enqueue(object : Callback { client.newCall(clientReq).enqueue(object : Callback {

View File

@ -1,7 +1,9 @@
package com.example.mpdriver.api package com.example.mpdriver.api
import android.content.Context import android.content.Context
import android.provider.ContactsContract.Data
import android.util.Log import android.util.Log
import com.example.mpdriver.storage.CreateUpdateTaskData
import com.example.mpdriver.storage.Database import com.example.mpdriver.storage.Database
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -9,9 +11,15 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import ru.gildor.coroutines.okhttp.await
import java.io.IOException
import kotlin.math.log
data class SetTaskStatusRequest( data class SetTaskStatusRequest(
@ -93,7 +101,37 @@ data class Route (
class TaskApi(ctx: Context) : Api(ctx) { class TaskApi(ctx: Context) : Api(ctx) {
suspend fun getPlannedTasksApiCall(errorHandler: (Exception)-> Unit = {}, handler: (List<TaskResponse>) -> Unit) { data class SendTaskStatusReq(
val data: List<CreateUpdateTaskData>
)
fun send_task_status_chains(onResponse: (Response) -> Unit = {}, onFailure: () -> Unit = {}) {
val chainBody = SendTaskStatusReq(Database.updateTasks).toJson().toRequestBody()
val req = Request.Builder().url("$BASE_URL/tasks").post(chainBody).build()
client.newCall(req).enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("taskAPI", "${e}")
onFailure()
}
override fun onResponse(call: Call, response: Response) {
Log.d("taskAPI", response.body.toString())
if (response.isSuccessful) {
return onResponse(response)
}
return onFailure()
}
}
)
}
suspend fun getPlannedTasksApiCall(
errorHandler: (Exception) -> Unit = {},
handler: (List<TaskResponse>) -> Unit
) {
val req = Request.Builder() val req = Request.Builder()
.url("$BASE_URL/tasks/planned") .url("$BASE_URL/tasks/planned")
.build() .build()
@ -103,21 +141,24 @@ class TaskApi(ctx: Context): Api(ctx) {
} }
} }
fun getPlannedTasks(errorHandler: (Exception)-> Unit = {}, handler: (List<TaskResponse>) -> Unit) { // fun getPlannedTasks(errorHandler: (Exception)-> Unit = {}, handler: (List<TaskResponse>) -> Unit) {
runBlocking { // runBlocking {
launch(Dispatchers.IO) { // launch(Dispatchers.IO) {
getPlannedTasksApiCall { // getPlannedTasksApiCall {
println(Database.planned_tasks) // println(Database.planned_tasks)
println(it) // println(it)
Database.planned_tasks = it // Database.planned_tasks = it
println(Database.planned_tasks) // println(Database.planned_tasks)
} // }
} // }
handler(Database.planned_tasks) // handler(Database.planned_tasks)
} // }
} // }
suspend fun getActiveTaskApiCall(errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { suspend fun getActiveTaskApiCall(
errorHandler: (Exception) -> Unit = {},
handler: (TaskResponse) -> Unit
) {
val req = Request.Builder() val req = Request.Builder()
.url("$BASE_URL/tasks/active") .url("$BASE_URL/tasks/active")
.build() .build()
@ -126,21 +167,24 @@ class TaskApi(ctx: Context): Api(ctx) {
handler(it.parse<TaskResponse>()) handler(it.parse<TaskResponse>())
} }
} }
fun getActiveTask(errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { // fun getActiveTask(errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) {
runBlocking { // runBlocking {
launch(Dispatchers.IO) { // launch(Dispatchers.IO) {
getAllTasksForUser() // getAllTasksForUser()
} // }
//
// val data = Database.tasks.filter { it.status == "InProgress" }
// if (data.count() >= 1) {
// return@runBlocking handler(data[0])
// }
// handler(TaskResponse())
// }
// }
val data = Database.tasks.filter { it.status == "InProgress" } fun getCompletedTask(
if (data.count() >= 1) { errorHandler: (Exception) -> Unit = {},
return@runBlocking handler(data[0]) handler: (List<TaskResponse>) -> Unit
} ) {
handler(TaskResponse())
}
}
fun getCompletedTask(errorHandler: (Exception)-> Unit = {}, handler: (List<TaskResponse>) -> Unit) {
val req = Request.Builder() val req = Request.Builder()
.url("$BASE_URL/tasks/completed") .url("$BASE_URL/tasks/completed")
.build() .build()
@ -149,7 +193,12 @@ class TaskApi(ctx: Context): Api(ctx) {
handler(it.parseList<TaskResponse>()) handler(it.parseList<TaskResponse>())
} }
} }
fun getSubtasks(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (List<Subtasks>) -> Unit) {
fun getSubtasks(
taskId: Long,
errorHandler: (Exception) -> Unit = {},
handler: (List<Subtasks>) -> Unit
) {
val req = Request.Builder() val req = Request.Builder()
.url("$BASE_URL/tasks/$taskId/subtasks") .url("$BASE_URL/tasks/$taskId/subtasks")
.build() .build()
@ -158,7 +207,12 @@ class TaskApi(ctx: Context): Api(ctx) {
handler(it.parseList<Subtasks>()) handler(it.parseList<Subtasks>())
} }
} }
fun getEvents(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (List<Events>) -> Unit) {
fun getEvents(
taskId: Long,
errorHandler: (Exception) -> Unit = {},
handler: (List<Events>) -> Unit
) {
val req = Request.Builder() val req = Request.Builder()
.url("$BASE_URL/tasks/$taskId/events") .url("$BASE_URL/tasks/$taskId/events")
.build() .build()
@ -168,24 +222,24 @@ class TaskApi(ctx: Context): Api(ctx) {
} }
} }
fun getAllTasksForUser() { // fun getAllTasksForUser() {
val req = Request.Builder().url("$BASE_URL/tasks").build() // val req = Request.Builder().url("$BASE_URL/tasks").build()
performRequest(req) {res -> // performRequest(req) {res ->
val data = res.parseList<TaskResponse>() // val data = res.parseList<TaskResponse>()
Database.tasks = data // Database.tasks = data
} // }
} // }
fun setTaskStatusInProgress(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) { // fun setTaskStatusInProgress(taskId: Long, errorHandler: (Exception)-> Unit = {}, handler: (TaskResponse) -> Unit) {
//
val body = SetTaskStatusRequest(taskId).toJson().toRequestBody("application/json".toMediaType()) // val body = SetTaskStatusRequest(taskId).toJson().toRequestBody("application/json".toMediaType())
//
val req = Request.Builder() // val req = Request.Builder()
.url("$BASE_URL/tasks/active") // .url("$BASE_URL/tasks/active")
.post(body) // .post(body)
//
performRequest(req.build(), errorHandler) { // performRequest(req.build(), errorHandler) {
handler(it.parse()) // handler(it.parse())
} // }
} // }
} }

View File

@ -0,0 +1,31 @@
package com.example.mpdriver.api
import androidx.compose.ui.platform.LocalContext
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.network.http.DefaultHttpEngine
import com.example.mpdriver.FetchApplicationDataQuery
import com.example.mpdriver.storage.Database
import com.tencent.mmkv.MMKV
val apolloClient = ApolloClient.Builder().serverUrl("http://192.168.0.101:8000/graphql").httpEngine(DefaultHttpEngine(timeoutMillis = 60000)).build()
suspend fun ApolloClient.fetchAppDataToDB(): Unit {
val kv = Database.kv
val req = this.query(FetchApplicationDataQuery("1125904232173609")).execute().data
req?.let {
for (task in req.tasks) {
kv.encode("task:${task.id}", task.toJson())
}
for (subtask in req.subtasks) {
kv.encode("subtask:${subtask.id}", subtask.toJson())
}
for (note in req.notes) {
kv.encode("note:${note.id}", note.toJson())
}
}
}

View File

@ -1,5 +1,6 @@
package com.example.mpdriver.components package com.example.mpdriver.components
import android.util.Log
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.Notifications
@ -39,13 +41,20 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight 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.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.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -54,9 +63,17 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.toInput
import com.example.mpdriver.GetSubtaskByIDQuery
import com.example.mpdriver.GetTaskByIdQuery
import com.example.mpdriver.R import com.example.mpdriver.R
import com.example.mpdriver.api.Subtasks import com.example.mpdriver.api.Subtasks
import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.recievers.TimeTickReciever import com.example.mpdriver.recievers.TimeTickReciever
import com.example.mpdriver.storage.CreateUpdateTaskData
import com.example.mpdriver.storage.Database
import com.example.mpdriver.type.StatusEnumQl
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -64,10 +81,13 @@ import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.UtcOffset import kotlinx.datetime.UtcOffset
import kotlinx.datetime.asTimeZone import kotlinx.datetime.asTimeZone
import kotlinx.datetime.format.byUnicodePattern import kotlinx.datetime.format.byUnicodePattern
import kotlinx.datetime.format.char
import kotlinx.datetime.toInstant import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlinx.datetime.until import kotlinx.datetime.until
import kotlin.math.abs import kotlin.math.abs
@ -76,7 +96,7 @@ import kotlin.math.abs
fun Subtask( fun Subtask(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
children: @Composable () -> Unit = {}, children: @Composable () -> Unit = {},
subtask: Subtasks = Subtasks(1, startPln = "2024-06-01T00:00", endPln = "2024-06-10T00:00"), subtaskID: Long = 0,
footerButton: @Composable () -> Unit = {} footerButton: @Composable () -> Unit = {}
) { ) {
@ -91,13 +111,39 @@ fun Subtask(
var nowTime by remember { var nowTime by remember {
mutableStateOf(Clock.System.now()) mutableStateOf(Clock.System.now())
} }
var subtaskResponse by remember {
mutableStateOf<GetSubtaskByIDQuery.Subtask?>(null)
}
LaunchedEffect(Unit) {
val sbtDB = Database.subtasks.find { it.id == subtaskID.toString() }
sbtDB?.let {
subtaskResponse = GetSubtaskByIDQuery.Subtask(
sbtDB.id,
sbtDB.text,
startPln = sbtDB.startPln,
endPln = sbtDB.endPln,
status = sbtDB.status
)
}
// subtaskResponse = apolloClient.query(GetSubtaskByIDQuery("1125904232173609", subtaskID.toString())).execute()
}
TimeTickReciever.registerHandler { TimeTickReciever.registerHandler {
nowTime = Clock.System.now() nowTime = Clock.System.now()
} }
val startPln = LocalDateTime.parse(subtask.startPln!!) var startPln: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.UTC)
val endPln = LocalDateTime.parse(subtask.endPln!!) var endPln: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.UTC)
subtaskResponse?.let {
startPln = LocalDateTime.parse(subtaskResponse?.startPln.toString())
endPln = LocalDateTime.parse(subtaskResponse?.endPln.toString())
}
val dateFormat = LocalDateTime.Format { val dateFormat = LocalDateTime.Format {
byUnicodePattern("d.MM.yyyy") byUnicodePattern("d.MM.yyyy")
@ -119,10 +165,10 @@ fun Subtask(
val status = when { val status = when {
subtask.status == "Completed" && !isDelay -> TaskStatus.SUCCESS subtaskResponse?.status == StatusEnumQl.COMPLETED && !isDelay -> TaskStatus.SUCCESS
subtask.status == "Completed" && isDelay -> TaskStatus.WARNING subtaskResponse?.status == StatusEnumQl.COMPLETED && isDelay -> TaskStatus.WARNING
subtask.status == "Cancelled" -> TaskStatus.WARNING subtaskResponse?.status == StatusEnumQl.CANCELLED -> TaskStatus.WARNING
subtask.status == "InProgress" || subtask.status == "NotDefined" && isDelay -> TaskStatus.DANGER (subtaskResponse?.status == StatusEnumQl.IN_PROGRESS || subtaskResponse?.status == StatusEnumQl.NOT_DEFINED) && isDelay -> TaskStatus.DANGER
else -> TaskStatus.DEFAULT else -> TaskStatus.DEFAULT
} }
@ -151,7 +197,7 @@ fun Subtask(
) { ) {
Text( Text(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
text = "${subtask.text}", text = "${subtaskResponse?.text}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 20.sp fontSize = 20.sp
) )
@ -259,8 +305,8 @@ fun Subtask(
} }
} }
if (isActionVisible) { if (isActionVisible && subtaskResponse != null) {
IsSubtaskCompletedAction(setStateAction = { isActionVisible = false }, subtask) IsSubtaskCompletedAction(setStateAction = { isActionVisible = false }, subtaskResponse!!)
} }
} }
@ -268,7 +314,10 @@ fun Subtask(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun IsSubtaskCompletedAction(setStateAction: (Boolean) -> Unit = { }, subtask: Subtasks) { fun IsSubtaskCompletedAction(
setStateAction: (Boolean) -> Unit = { },
subtask: GetSubtaskByIDQuery.Subtask
) {
var currentStep by remember { var currentStep by remember {
mutableStateOf(0) mutableStateOf(0)
} }
@ -318,6 +367,10 @@ fun IsSubtaskCompletedAction(setStateAction: (Boolean) -> Unit = { }, subtask: S
composable("success") { composable("success") {
SuccessStep(subtask = subtask, controller = actionController) SuccessStep(subtask = subtask, controller = actionController)
title = "Когда вы выполнили подзадачу?" title = "Когда вы выполнили подзадачу?"
isFullScreen = true
LaunchedEffect(sheetState) {
sheetState.expand()
}
} }
composable("failure") { composable("failure") {
isFullScreen = true isFullScreen = true
@ -339,7 +392,7 @@ fun IsSubtaskCompletedAction(setStateAction: (Boolean) -> Unit = { }, subtask: S
@Composable @Composable
fun InitialStep(subtask: Subtasks, controller: NavHostController) { fun InitialStep(subtask: GetSubtaskByIDQuery.Subtask, controller: NavHostController) {
Column { Column {
JDEButton(type = ButtonType.SUCCESS, onClick = { controller.navigate("success") }) { JDEButton(type = ButtonType.SUCCESS, onClick = { controller.navigate("success") }) {
Text(text = "Подзадача выполнена", fontSize = 15.sp, fontWeight = FontWeight.Bold) Text(text = "Подзадача выполнена", fontSize = 15.sp, fontWeight = FontWeight.Bold)
@ -354,10 +407,9 @@ fun InitialStep(subtask: Subtasks, controller: NavHostController) {
} }
@Preview(showBackground = true)
@Composable @Composable
fun SuccessStep( fun SuccessStep(
subtask: Subtasks = Subtasks(), subtask: GetSubtaskByIDQuery.Subtask,
controller: NavHostController = rememberNavController() controller: NavHostController = rememberNavController()
) { ) {
@ -402,26 +454,96 @@ fun SuccessStep(
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
TextInput(modifier = Modifier.weight(.66f), date, { date = it }, "Дата") TextInput(modifier = Modifier.weight(.66f), date, {
date = it.filter { it.isDigit() }
}, "Дата")
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
TextInput(modifier = Modifier.weight(.33f), time, { date = time }, "Время") TextInput(
modifier = Modifier.weight(.33f),
time,
{ time = it.filter { it.isDigit() } },
"Время"
)
} }
} }
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
ActiveButton(onClick = { /*TODO*/ }, text = "Сохранить", modifier = Modifier.fillMaxWidth()) ActiveButton(onClick = {
val task = Database.tasks.find {
it.subtasks.find { sbt ->
sbt.id == subtask.id
} != null
}
val dt = LocalDateTime.parse(date + "T" + time, LocalDateTime.Format {
dayOfMonth()
monthNumber()
year()
char('T')
hour()
minute()
}).toInstant(TimeZone.UTC).toEpochMilliseconds()
task?.let {
Database.createUpdateTaskDataLocally(
CreateUpdateTaskData(
subtask.id.toLong(),
dt = dt,
status = "Completed"
)
)
if (subtask.id == task.subtasks.last().id) {
Database.createUpdateTaskDataLocally(
CreateUpdateTaskData(
task.id.toLong(),
dt = dt,
status = "Completed"
)
)
return@let
}
val curr_sbt_idx = task.subtasks.indexOf(task.subtasks.find { it.id == subtask.id })
Database.createUpdateTaskDataLocally(
CreateUpdateTaskData(
task.subtasks[curr_sbt_idx + 1].id.toLong(),
dt,
status = "InProgress"
)
)
}
}, text = "Сохранить", modifier = Modifier.fillMaxWidth())
} }
} }
fun transformText(it: String): String {
var digits = it.filter { it.isDigit() }
if (digits.length > 8) {
digits = digits.slice(0..<8)
}
var data = when (digits.length) {
in 0..2 -> "${digits}"
in 3..4 -> "${digits.substring(0..<2)}.${digits.substring(2)}"
in 5..8 -> "${digits.substring(0..<2)}.${digits.substring(2..<4)}.${digits.substring(4)}"
else -> digits
}
println(data)
return data
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable @Composable
fun FailureStep( fun FailureStep(
subtask: Subtasks = Subtasks(), subtask: GetSubtaskByIDQuery.Subtask,
controller: NavHostController = rememberNavController() controller: NavHostController = rememberNavController()
) { ) {
@ -440,7 +562,8 @@ fun FailureStep(
color = Color.Gray color = Color.Gray
) )
Spacer(modifier = Modifier.height(40.dp)) Spacer(modifier = Modifier.height(40.dp))
TextField(value = failureDesk, TextField(
value = failureDesk,
onValueChange = { failureDesk = it }, onValueChange = { failureDesk = it },
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -466,10 +589,12 @@ fun FailureStep(
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent unfocusedIndicatorColor = Color.Transparent
), ),
trailingIcon = {Icon( trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.calendar_default), painter = painterResource(id = R.drawable.calendar_default),
contentDescription = "" contentDescription = ""
)} )
}
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
@ -485,15 +610,19 @@ fun FailureStep(
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent unfocusedIndicatorColor = Color.Transparent
), ),
trailingIcon = {Icon(painter = painterResource(id = R.drawable.calendar_default), trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.calendar_default),
contentDescription = "" contentDescription = ""
)} )
}
) )
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
ActiveButton(onClick = { /*TODO*/ }, text = "Отправить", modifier = Modifier.fillMaxWidth()) ActiveButton(onClick = { /*TODO*/ }, text = "Отправить", modifier = Modifier.fillMaxWidth())
} }
} }
@OptIn(ExperimentalComposeUiApi::class)
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun TextInput( fun TextInput(
@ -507,6 +636,8 @@ fun TextInput(
mutableStateOf(true) mutableStateOf(true)
} }
val kbController = LocalSoftwareKeyboardController.current
BasicTextField( BasicTextField(
modifier = modifier modifier = modifier
@ -515,7 +646,13 @@ fun TextInput(
}, },
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = {
kbController?.hide()
}),
decorationBox = { decorationBox = {
Box( Box(
Modifier Modifier
@ -525,16 +662,16 @@ fun TextInput(
.padding(horizontal = 10.dp, vertical = 5.dp) .padding(horizontal = 10.dp, vertical = 5.dp)
) { ) {
Text( Text(
modifier = Modifier.align(if (isFocused) Alignment.TopStart else Alignment.CenterStart), modifier = Modifier.align(if (isFocused || value != "") Alignment.TopStart else Alignment.CenterStart),
text = placeholder, text = placeholder,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = if (isFocused) 13.sp else 18.sp, fontSize = if (isFocused || value != "") 13.sp else 18.sp,
color = Color.Gray color = Color.Gray
) )
if (isFocused) { if (isFocused || value != "") {
Text( Text(
modifier = Modifier.align(Alignment.BottomStart), modifier = Modifier.align(Alignment.BottomStart),
text = value, text = transformText(value),
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )

View File

@ -14,12 +14,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -32,10 +34,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.mpdriver.GetTaskByIdQuery
import com.example.mpdriver.R import com.example.mpdriver.R
import com.example.mpdriver.api.Subtasks import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.api.TaskResponse
import com.example.mpdriver.recievers.TimeTickReciever import com.example.mpdriver.recievers.TimeTickReciever
import com.example.mpdriver.storage.Database
import com.example.mpdriver.type.MarshTemperaturePropertyQL
import com.example.mpdriver.type.StatusEnumQl
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
@ -46,7 +51,6 @@ import kotlinx.datetime.toInstant
import kotlinx.datetime.until import kotlinx.datetime.until
import kotlin.math.abs import kotlin.math.abs
enum class TaskStatus { enum class TaskStatus {
DANGER, DANGER,
SUCCESS, SUCCESS,
@ -58,8 +62,47 @@ enum class TaskStatus {
@Composable @Composable
fun Task(modifier: Modifier = Modifier, fun Task(modifier: Modifier = Modifier,
children: @Composable() ()->Unit = {}, children: @Composable() ()->Unit = {},
taskResponse: TaskResponse = TaskResponse(1, "2024-06-01T00:00", "2024-06-10T00:00"), task_id: Long = 1,
footerButton: @Composable ()-> Unit = {}) { footerButton: @Composable ()-> Unit = {}) {
var taskResponse by remember {
mutableStateOf<GetTaskByIdQuery.Data?>(null)
}
LaunchedEffect(Unit) {
val dbTask = Database.tasks.find { it.id == task_id.toString() }
var task: GetTaskByIdQuery.Task
dbTask?.let {
val trailer = when(dbTask.route.trailer) {
null -> null
else -> GetTaskByIdQuery.Trailer(gost = dbTask.route.trailer.gost)
}
val truck = when(dbTask.route.truck) {
null -> null
else -> GetTaskByIdQuery.Truck(gost = dbTask.route.truck.gost)
}
val route = GetTaskByIdQuery.Route(name = dbTask.route.name, dbTask.route.temperatureProperty, truck, trailer)
task = GetTaskByIdQuery.Task(status = dbTask.status, route, text = dbTask.text, startPln = dbTask.startPln, endPln = dbTask.endPln, startFact = dbTask.startFact, endFact = dbTask.endFact)
taskResponse = GetTaskByIdQuery.Data(task)
return@LaunchedEffect
}
taskResponse = apolloClient.query(GetTaskByIdQuery(task_id.toString(), "1125904232173609")).execute().data
}
val dateFormat = LocalDateTime.Format {
byUnicodePattern("d.MM.yyyy")
}
val timeFormat = LocalDateTime.Format {
byUnicodePattern("HH:mm")
}
var expanded by remember { var expanded by remember {
mutableStateOf(false) mutableStateOf(false)
} }
@ -72,17 +115,9 @@ fun Task(modifier: Modifier = Modifier,
nowTime = Clock.System.now() nowTime = Clock.System.now()
} }
val startPln = LocalDateTime.parse(taskResponse.startPln!!) taskResponse?.let {
val endPln = LocalDateTime.parse(taskResponse.endPln!!) val startPln = LocalDateTime.parse(it.task!!.startPln.toString())
val endPln = LocalDateTime.parse(it.task.endPln.toString())
val dateFormat = LocalDateTime.Format {
byUnicodePattern("d.MM.yyyy")
}
val timeFormat = LocalDateTime.Format {
byUnicodePattern("HH:mm")
}
val leastBase = nowTime.until( val leastBase = nowTime.until(
endPln.toInstant(offset = UtcOffset(3)), endPln.toInstant(offset = UtcOffset(3)),
timeZone = UtcOffset(3).asTimeZone(), unit = DateTimeUnit.MINUTE) timeZone = UtcOffset(3).asTimeZone(), unit = DateTimeUnit.MINUTE)
@ -94,16 +129,15 @@ fun Task(modifier: Modifier = Modifier,
val status = when { val status = when {
taskResponse.status == "Completed" && !isDelay -> TaskStatus.SUCCESS it.task.status == StatusEnumQl.COMPLETED && !isDelay -> TaskStatus.SUCCESS
taskResponse.status == "Completed" && isDelay -> TaskStatus.WARNING it.task.status == StatusEnumQl.COMPLETED && isDelay -> TaskStatus.WARNING
taskResponse.status == "Cancelled" -> TaskStatus.WARNING it.task.status == StatusEnumQl.CANCELLED -> TaskStatus.WARNING
taskResponse.status == "InProgress" || taskResponse.status == "NotDefined" && isDelay -> TaskStatus.DANGER (it.task.status == StatusEnumQl.IN_PROGRESS || it.task.status == StatusEnumQl.NOT_DEFINED) && isDelay -> TaskStatus.DANGER
else -> TaskStatus.DEFAULT else -> TaskStatus.DEFAULT
} }
return Column(
Column(
modifier modifier
.border( .border(
2.dp, when (status) { 2.dp, when (status) {
@ -124,7 +158,7 @@ fun Task(modifier: Modifier = Modifier,
) { ) {
Text( Text(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
text = "Движение по маршруту\n${taskResponse.route?.name}", text = "Движение по маршруту\n${it.task.route.name}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 20.sp fontSize = 20.sp
) )
@ -140,13 +174,13 @@ fun Task(modifier: Modifier = Modifier,
Row(Modifier.padding(bottom = 20.dp)) { Row(Modifier.padding(bottom = 20.dp)) {
InformationPlaceholderSmall( InformationPlaceholderSmall(
Modifier.weight(1f), Modifier.weight(1f),
mainText = "${taskResponse.route?.truck?.gost}", mainText = "${if (it.task.route.truck != null) it.task.route.truck.gost else "-"}",
subText = "Номер ТС" subText = "Номер ТС"
) )
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
InformationPlaceholderSmall( InformationPlaceholderSmall(
Modifier.weight(1f), Modifier.weight(1f),
mainText = "${if (taskResponse.route!!.trailer != null) taskResponse.route!!.trailer!!.gost else "-"}", mainText = "${if (it.task.route.trailer != null) it.task.route.trailer.gost else "-"}",
subText = "Номер прицепа" subText = "Номер прицепа"
) )
} }
@ -154,11 +188,11 @@ fun Task(modifier: Modifier = Modifier,
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 20.dp), .padding(bottom = 20.dp),
mainText = if (taskResponse.route!!.temperatureProperty == 1) "Горячая" else "Холодная", mainText = if (it.task.route.temperatureProperty == MarshTemperaturePropertyQL.HOT) "Горячая" else "Холодная",
subText = "Тип перевозки" subText = "Тип перевозки"
) { ) {
when (taskResponse.route!!.temperatureProperty) { when (it.task.route.temperatureProperty) {
1 -> Image(painter = painterResource(id = R.drawable.hottransport), contentDescription = "" ) MarshTemperaturePropertyQL.HOT -> Image(painter = painterResource(id = R.drawable.hottransport), contentDescription = "" )
else -> Image(painter = painterResource(id = R.drawable.coltransport), contentDescription = "" ) else -> Image(painter = painterResource(id = R.drawable.coltransport), contentDescription = "" )
} }
@ -170,7 +204,7 @@ fun Task(modifier: Modifier = Modifier,
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 20.dp), .padding(bottom = 20.dp),
mainText = "${taskResponse.text}", mainText = it.task.text,
subText = "Полный маршрут" subText = "Полный маршрут"
) )
@ -270,7 +304,19 @@ fun Task(modifier: Modifier = Modifier,
} }
} }
footerButton() footerButton()
} }
} }
return Column (
Modifier
.fillMaxWidth()
.padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(color = Color(0xFFE5332A))
}
}

View File

@ -25,6 +25,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -39,24 +40,44 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.apollographql.apollo3.api.ApolloResponse
import com.example.mpdriver.GetActiveSubtaskIDQuery
import com.example.mpdriver.GetActiveTaskIdQuery
import com.example.mpdriver.GetTaskByIdQuery
import com.example.mpdriver.api.TaskResponse import com.example.mpdriver.api.TaskResponse
import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.components.ButtonType import com.example.mpdriver.components.ButtonType
import com.example.mpdriver.components.EmptyList import com.example.mpdriver.components.EmptyList
import com.example.mpdriver.components.IteractionButton import com.example.mpdriver.components.IteractionButton
import com.example.mpdriver.components.JDEButton import com.example.mpdriver.components.JDEButton
import com.example.mpdriver.components.Subtask import com.example.mpdriver.components.Subtask
import com.example.mpdriver.components.Task import com.example.mpdriver.components.Task
import com.example.mpdriver.storage.Database
import com.example.mpdriver.type.StatusEnumQl
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ActiveTask(activeTask: TaskResponse? = null, hostController: NavHostController) { fun ActiveTask(activeTaskID: Long? = null, hostController: NavHostController) {
var responseGetActiveSubtaskID by remember {
mutableStateOf<GetActiveSubtaskIDQuery.ActiveSubtask?>(null)
}
activeTaskID?.let {
LaunchedEffect(Unit) {
val sbtDB = Database.subtasks.find { it.status == StatusEnumQl.IN_PROGRESS }
sbtDB?.let {
responseGetActiveSubtaskID = GetActiveSubtaskIDQuery.ActiveSubtask(sbtDB.id)
}
}
}
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 15.dp) .padding(vertical = 15.dp)
) { ) {
if (activeTask == null) { if (activeTaskID == null) {
EmptyList(Modifier.padding(vertical = 60.dp), text = "У вас нет активных задач") EmptyList(Modifier.padding(vertical = 60.dp), text = "У вас нет активных задач")
return return
} }
@ -66,9 +87,9 @@ fun ActiveTask(activeTask: TaskResponse? = null, hostController: NavHostControll
fontSize = 18.sp fontSize = 18.sp
) )
activeTask.let { activeTaskID.let {
IteractionButton(onClick = { hostController.navigate("tasks/${it.id}") }) { IteractionButton(onClick = { hostController.navigate("tasks/${activeTaskID}") }) {
Task(taskResponse = it) Task(task_id = activeTaskID)
} }
} }
@ -81,14 +102,14 @@ fun ActiveTask(activeTask: TaskResponse? = null, hostController: NavHostControll
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
activeTask.let { task -> responseGetActiveSubtaskID.let { task ->
IteractionButton(onClick = { /*TODO*/ }) { IteractionButton(onClick = { /*TODO*/ }) {
Subtask(subtask = task.subtasks.filter { it.status == "InProgress" }[0]) responseGetActiveSubtaskID?.let {
Subtask(subtaskID = it.id.toLong())
}
}
} }
} }
} }
}

View File

@ -0,0 +1,35 @@
package com.example.mpdriver.components.note
import androidx.compose.foundation.border
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.foundation.shape.RoundedCornerShape
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.mpdriver.components.ActiveButton
import com.example.mpdriver.components.JDEButton
@Preview(showBackground = true)
@Composable
fun NoteComponent(modifier: Modifier = Modifier) {
Column(
modifier
.fillMaxWidth()
.border(2.dp, color = Color.Gray, shape = RoundedCornerShape(10.dp))
.padding(15.dp)) {
Text(text = "Изменился статус события", fontSize = 18.sp, fontWeight = FontWeight.Bold)
Text(text = "Изменился статус события\n“Прибыть на ПГР 20.12.2023 к 10:00”", fontSize = 15.sp, fontWeight = FontWeight.Normal, color = Color.Gray)
Spacer(modifier = Modifier.height(10.dp))
ActiveButton(onClick = { /*TODO*/ }, text = "Перейти к задаче", modifier = Modifier.fillMaxWidth())
}
}

View File

@ -1,6 +1,7 @@
package com.example.mpdriver.screens package com.example.mpdriver.screens
import android.os.Build import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -19,11 +21,17 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.apollographql.apollo3.api.ApolloResponse
import com.example.mpdriver.GetActiveTaskIdQuery
import com.example.mpdriver.GetTaskByIdQuery
import com.example.mpdriver.api.TaskApi import com.example.mpdriver.api.TaskApi
import com.example.mpdriver.api.TaskResponse import com.example.mpdriver.api.TaskResponse
import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.components.Layout import com.example.mpdriver.components.Layout
import com.example.mpdriver.components.feed.ActiveTask import com.example.mpdriver.components.feed.ActiveTask
import com.example.mpdriver.components.feed.FeedTaskDataCard import com.example.mpdriver.components.feed.FeedTaskDataCard
import com.example.mpdriver.storage.Database
import com.example.mpdriver.type.StatusEnumQl
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format.byUnicodePattern import kotlinx.datetime.format.byUnicodePattern
@ -37,29 +45,17 @@ fun Feed(modifier: Modifier = Modifier, hostController: NavHostController) {
mutableStateOf(true) mutableStateOf(true)
} }
val context = LocalContext.current
var activeTask by remember { var activeTask by remember {
mutableStateOf<TaskResponse?>(null) mutableStateOf<GetActiveTaskIdQuery.Task?>(null)
} }
var plannedTasks by remember { var plannedTasks by remember {
mutableStateOf<List<TaskResponse>>(emptyList()) mutableStateOf(emptyList<GetActiveTaskIdQuery.PlannedTask>())
} }
var completedTasks by remember { var completedTasks by remember {
mutableStateOf<List<TaskResponse>>(emptyList()) mutableStateOf(emptyList<GetActiveTaskIdQuery.CompletedTask>())
} }
val taskApi = TaskApi(context)
taskApi.getActiveTask { task ->
isLoading = false
task.id?.let {
activeTask = task
}
}
taskApi.getPlannedTasks { tasks ->
plannedTasks = tasks
}
val dateFormat = LocalDateTime.Format { val dateFormat = LocalDateTime.Format {
byUnicodePattern("dd.MM.yyyy") byUnicodePattern("dd.MM.yyyy")
} }
@ -70,7 +66,7 @@ fun Feed(modifier: Modifier = Modifier, hostController: NavHostController) {
"count" to plannedTasks.count(), "count" to plannedTasks.count(),
"date" to when (plannedTasks.count()) { "date" to when (plannedTasks.count()) {
0 -> "-" 0 -> "-"
else -> dateFormat.format(LocalDateTime.parse(plannedTasks[0].startPln!!)) else -> dateFormat.format(LocalDateTime.parse(plannedTasks[0].startPln.toString()))
}, },
"buttonLabel" to "Смотреть запланированные задачи", "buttonLabel" to "Смотреть запланированные задачи",
"dateDescription" to "Ближайшая", "dateDescription" to "Ближайшая",
@ -80,23 +76,59 @@ fun Feed(modifier: Modifier = Modifier, hostController: NavHostController) {
"count" to completedTasks.count(), "count" to completedTasks.count(),
"date" to when (completedTasks.count()) { "date" to when (completedTasks.count()) {
0 -> "-" 0 -> "-"
else -> dateFormat.format(LocalDateTime.parse(completedTasks[0].startPln!!)) else -> dateFormat.format(LocalDateTime.parse(completedTasks[0].startPln.toString()))
}, },
"buttonLabel" to "Смотреть завершенные задачи", "buttonLabel" to "Смотреть завершенные задачи",
"dateDescription" to "Последняя", "dateDescription" to "Последняя",
), ),
) )
LaunchedEffect(Unit) {
val tasks = Database.tasks
val activeTaskDB = tasks.find { it.status == StatusEnumQl.IN_PROGRESS }
val completedTasksDB = tasks.filter { it.status == StatusEnumQl.COMPLETED }
val plannedTasksDB = tasks.filter { it.status == StatusEnumQl.NOT_DEFINED }
activeTask = when (activeTaskDB) {
null -> null
else -> GetActiveTaskIdQuery.Task(id = activeTaskDB.id)
}
completedTasks = when (completedTasksDB.count()) {
0 -> emptyList()
else -> completedTasksDB.map {t ->
GetActiveTaskIdQuery.CompletedTask(t.id, t.startPln)
}
}
plannedTasks = when (plannedTasksDB.count()) {
0 -> emptyList()
else -> plannedTasksDB.map { t ->
GetActiveTaskIdQuery.PlannedTask(t.id, t.startPln)
}
}
Log.d("FEED:PlannedTasks", "${plannedTasks}")
Log.d("FEED:ActiveTask", "${activeTask}")
Log.d("FEED:CompletedTask", "${completedTasks}")
isLoading = false
}
if(isLoading) { if(isLoading) {
Column (Modifier.fillMaxWidth().padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column (
Modifier
.fillMaxWidth()
.padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(color = Color(0xFFE5332A)) CircularProgressIndicator(color = Color(0xFFE5332A))
} }
return return
} }
Layout(dataList = dataList, header = { Layout(dataList = dataList, header = {
Column { Column {
ActiveTask(activeTask = activeTask, hostController = hostController) ActiveTask(activeTaskID = activeTask?.id?.toLong(), hostController = hostController)
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
} }

View File

@ -0,0 +1,17 @@
package com.example.mpdriver.screens
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.mpdriver.components.Layout
import com.example.mpdriver.components.note.NoteComponent
@Preview(showBackground = true)
@Composable
fun NoteScreen() {
Layout(dataList = (0..5).toMutableList()) {
NoteComponent()
}
}

View File

@ -25,6 +25,7 @@ import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -40,6 +41,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import com.example.mpdriver.FetchApplicationDataQuery
import com.example.mpdriver.api.Subtasks import com.example.mpdriver.api.Subtasks
import com.example.mpdriver.api.TaskApi import com.example.mpdriver.api.TaskApi
import com.example.mpdriver.components.ComposableLifecycle import com.example.mpdriver.components.ComposableLifecycle
@ -47,6 +49,8 @@ import com.example.mpdriver.components.HeaderTabs
import com.example.mpdriver.components.HeaderTabsData import com.example.mpdriver.components.HeaderTabsData
import com.example.mpdriver.components.Layout import com.example.mpdriver.components.Layout
import com.example.mpdriver.components.Subtask import com.example.mpdriver.components.Subtask
import com.example.mpdriver.storage.Database
import kotlinx.datetime.LocalDateTime
@Preview(showBackground = true) @Preview(showBackground = true)
@ -65,8 +69,9 @@ fun SubtaskScreen(taskId: Long = 0) {
var datalist by remember { var datalist by remember {
mutableStateOf<List<Subtasks>>(mutableListOf()) mutableStateOf(emptyList<FetchApplicationDataQuery.Subtask1>())
} }
var activeTab by remember { var activeTab by remember {
mutableStateOf(0) mutableStateOf(0)
} }
@ -74,25 +79,24 @@ fun SubtaskScreen(taskId: Long = 0) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
var offset = listState.firstVisibleItemScrollOffset + listState.firstVisibleItemIndex var offset = listState.firstVisibleItemScrollOffset + listState.firstVisibleItemIndex
val tabsData = listOf<HeaderTabsData>(HeaderTabsData(0, "Подзадачи"), HeaderTabsData(1, "События")) val tabsData =
listOf<HeaderTabsData>(HeaderTabsData(0, "Подзадачи"), HeaderTabsData(1, "События"))
ComposableLifecycle { _, event -> LaunchedEffect(Unit) {
when (event) { val task = Database.tasks.find { it.id == taskId.toString() }
Lifecycle.Event.ON_CREATE -> { datalist = Database.subtasks.filter { sbt ->
api.getSubtasks(taskId) { task!!.subtasks.find { it.id == sbt.id } != null
datalist = it }.sortedBy { LocalDateTime.parse(it.startPln.toString()) }
isLoading = false isLoading = false
} }
}
else -> {}
}
}
if (isLoading) { if (isLoading) {
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { .padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(color = Color(0xFFE5332A)) CircularProgressIndicator(color = Color(0xFFE5332A))
} }
return return
@ -106,12 +110,20 @@ fun SubtaskScreen(taskId: Long = 0) {
activeTab = it activeTab = it
} }
}) { }) {
Subtask(subtask = it) Subtask(subtaskID = it.id!!.toLong())
} }
AnimatedVisibility(visible = offset + 200 > floatingActionShowOffset + 100, enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut()) { AnimatedVisibility(
ExtendedFloatingActionButton(onClick = { /*TODO*/ }, visible = offset + 200 > floatingActionShowOffset + 100,
shape = RoundedCornerShape(10.dp), containerColor = Color.White, contentColor = Color.Black) { enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut()
) {
ExtendedFloatingActionButton(
onClick = { /*TODO*/ },
shape = RoundedCornerShape(10.dp),
containerColor = Color.White,
contentColor = Color.Black
) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text(text = "Скрыть завершенные задачи") Text(text = "Скрыть завершенные задачи")
} }

View File

@ -16,6 +16,7 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -28,17 +29,25 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.apollographql.apollo3.api.ApolloResponse
import com.example.mpdriver.GetPlannedTasksIDsQuery
import com.example.mpdriver.GetTaskByIdQuery
import com.example.mpdriver.api.TaskApi import com.example.mpdriver.api.TaskApi
import com.example.mpdriver.api.TaskResponse import com.example.mpdriver.api.TaskResponse
import com.example.mpdriver.api.apolloClient
import com.example.mpdriver.components.ActiveButton import com.example.mpdriver.components.ActiveButton
import com.example.mpdriver.components.HeaderTabs import com.example.mpdriver.components.HeaderTabs
import com.example.mpdriver.components.HeaderTabsData import com.example.mpdriver.components.HeaderTabsData
import com.example.mpdriver.components.Layout import com.example.mpdriver.components.Layout
import com.example.mpdriver.components.StaleButton import com.example.mpdriver.components.StaleButton
import com.example.mpdriver.components.Task import com.example.mpdriver.components.Task
import com.example.mpdriver.storage.CreateUpdateTaskData
import com.example.mpdriver.storage.Database import com.example.mpdriver.storage.Database
import com.example.mpdriver.type.StatusEnumQl
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import java.time.LocalDateTime
@Composable @Composable
@ -50,26 +59,41 @@ fun TasksList(modifier: Modifier = Modifier, hostController: NavHostController)
} }
var itemsList by remember { var itemsListResponse by remember {
mutableStateOf<List<TaskResponse>>(emptyList()) mutableStateOf(emptyList<GetPlannedTasksIDsQuery.Task>())
}
api.getPlannedTasks {
itemsList = it
isLoading = false
} }
var activeTab by remember { var activeTab by remember {
mutableIntStateOf(0) mutableIntStateOf(0)
} }
LaunchedEffect(Unit) {
val d = Database.tasks.filter { it -> it.status == StatusEnumQl.NOT_DEFINED}
itemsListResponse = when(d.count()) {
0 -> emptyList()
else -> d.map {
val sbts = it.subtasks.map {sbt ->
GetPlannedTasksIDsQuery.Subtask(sbt.id)
}
GetPlannedTasksIDsQuery.Task(id = it.id, sbts)
}
}
isLoading = false
}
if(isLoading) { if(isLoading) {
Column (Modifier.fillMaxWidth().padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column (
Modifier
.fillMaxWidth()
.padding(vertical = 60.dp), horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(color = Color(0xFFE5332A)) CircularProgressIndicator(color = Color(0xFFE5332A))
} }
return return
} }
Layout(dataList = itemsList, header = { Layout(dataList = itemsListResponse, header = {
HeaderTabs( HeaderTabs(
tabsData = listOf( tabsData = listOf(
HeaderTabsData(0, "Запланированные"), HeaderTabsData(0, "Запланированные"),
@ -79,15 +103,24 @@ fun TasksList(modifier: Modifier = Modifier, hostController: NavHostController)
activeTab = it activeTab = it
} }
}) { }) {
Task(Modifier.padding(bottom = 10.dp), taskResponse = it) { Task(Modifier.padding(bottom = 10.dp), task_id = it.id.toLong()) {
when (activeTab) { when (activeTab) {
0 -> Button( 0 -> Button(
onClick = { onClick = {
api.setTaskStatusInProgress(it.id!!) { Database.createUpdateTaskDataLocally(
MainScope().launch { CreateUpdateTaskData(
it.id.toLong(),
Clock.System.now().toEpochMilliseconds(),
"InProgress"
)
)
Database.createUpdateTaskDataLocally(
CreateUpdateTaskData(
it.subtasks[0].id.toLong(), Clock.System.now().toEpochMilliseconds(), "InProgress",
)
)
hostController.navigate("feed") hostController.navigate("feed")
}
}
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = Color.White, contentColor = Color.White,
@ -96,7 +129,7 @@ fun TasksList(modifier: Modifier = Modifier, hostController: NavHostController)
disabledContainerColor = Color.Gray disabledContainerColor = Color.Gray
), ),
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
enabled = if (Database.tasks.filter { it.status == "InProgress" }.count() >= 1) false else true, // enabled = if (Database.tasks.filter { it.status == "InProgress" }.count() >= 1) false else true,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text(text = "Приступить к выполнению") Text(text = "Приступить к выполнению")

View File

@ -1,43 +1,28 @@
package com.example.mpdriver.storage package com.example.mpdriver.storage
import com.example.mpdriver.api.TaskResponse import android.util.Log
import com.benasher44.uuid.uuid4
import com.example.mpdriver.FetchApplicationDataQuery
import com.example.mpdriver.api.toJson
import com.example.mpdriver.type.DateTime
import com.example.mpdriver.type.StatusEnumQl
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import java.lang.reflect.Type
data class CreateUpdateTaskData(
val task_id: Long,
val dt: Long,
val status: String,
val error_text: String? = null
)
object Database { object Database {
val kv = MMKV.defaultMMKV() val kv = MMKV.defaultMMKV()
var tasks: List<TaskResponse> val allKeys: Array<out String>?
get() { get() = kv.allKeys()
val data = kv.decodeString("tasks")
data?.let { json ->
val type: Type = object : TypeToken<List<TaskResponse>>() {}.type
return Gson().fromJson(json, type)
}
return emptyList()
}
set(value) {
val data = Gson().toJson(value)
kv.encode("tasks", data)
}
var planned_tasks: List<TaskResponse>
get() {
val data = kv.decodeString("planned_tasks")
data?.let { json ->
val type: Type = object : TypeToken<List<TaskResponse>>() {}.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? var access_token: String?
get() { get() {
@ -52,4 +37,96 @@ object Database {
set(value) { set(value) {
kv.encode("phone_number", value) kv.encode("phone_number", value)
} }
private inline fun <reified D> parseData(prefix: String): MutableList<D> {
val returnedList = mutableListOf<D>()
for (key in allKeys!!) {
if (key.startsWith(prefix)) {
val data = kv.decodeString(key)
data.let {
returnedList.add(Gson().fromJson(it, D::class.java))
}
}
}
return returnedList
}
val tasks: MutableList<FetchApplicationDataQuery.Task>
get() = parseData("task:")
val notes: MutableList<FetchApplicationDataQuery.Note>
get() = parseData("note:")
val subtasks: MutableList<FetchApplicationDataQuery.Subtask1>
get() = parseData("subtask:")
val updateTasks: MutableList<CreateUpdateTaskData>
get() = parseData("update:task:")
fun createUpdateTaskDataLocally(d: CreateUpdateTaskData) {
kv.encode("update:task:${uuid4()}", d.toJson())
val task = tasks.find { it.id == d.task_id.toString() }
val sbt = subtasks.find { it.id == d.task_id.toString() }
task?.let {
val newTask = FetchApplicationDataQuery.Task(
id = task.id,
startPln = task.startPln,
startFact = task.startFact,
endPln = task.endPln,
endFact = task.endFact,
status = when (d.status) {
"InProgress" -> StatusEnumQl.IN_PROGRESS
"Completed" -> StatusEnumQl.COMPLETED
"Cancelled" -> StatusEnumQl.CANCELLED
else -> task.status
},
events = task.events,
subtasks = task.subtasks,
taskType = task.taskType,
route = task.route,
text = task.text
)
kv.encode("task:${newTask.id}", newTask.toJson())
// Log.d("MMKV: task:${newTask.id}", kv.decodeString("task:${newTask.id}")!!)
Log.d("MMKV.KEYS", allKeys!!.toMutableList().joinToString(", "))
}
sbt?.let { s ->
val newSubtask = FetchApplicationDataQuery.Subtask1(
id = s.id,
startPln = s.startPln,
startFact = s.startFact,
endPln = s.endPln,
endFact = s.endFact,
station = s.station,
status = when (d.status) {
"InProgress" -> StatusEnumQl.IN_PROGRESS
"Completed" -> StatusEnumQl.COMPLETED
"Cancelled" -> StatusEnumQl.CANCELLED
else -> s.status
},
taskType = s.taskType,
text = s.text
)
kv.encode("subtask:${newSubtask.id}", newSubtask.toJson())
Log.d("MMKV.KEYS", allKeys!!.toMutableList().joinToString(", "))
}
}
fun dropUpdates() {
for (key in allKeys!!){
if (key.startsWith("update:task:")) {
kv.remove(key)
}
}
}
} }

View File

@ -2,4 +2,5 @@
plugins { plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false
id("com.google.gms.google-services") version "4.4.2" apply false
} }