Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
TeamPrinterV2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Aleksandr
TeamPrinterV2
Commits
716c048f
Commit
716c048f
authored
Jul 16, 2024
by
Aleksandr
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Details screen (template)
parent
564e344c
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
485 additions
and
184 deletions
+485
-184
app/src/main/java/com/isidroid/c23/Errors.kt
+3
-2
app/src/main/java/com/isidroid/c23/constant/Argument.kt
+5
-0
app/src/main/java/com/isidroid/c23/data/mapper/PrintJobListItemMapper.kt
+30
-0
app/src/main/java/com/isidroid/c23/domain/use_case/DetailsUseCase.kt
+56
-0
app/src/main/java/com/isidroid/c23/domain/use_case/PrintJobsUseCase.kt
+4
-28
app/src/main/java/com/isidroid/c23/ui/navigation/destinations/JobDetailsDestination.kt
+21
-3
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsContract.kt
+13
-2
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsScreen.kt
+166
-10
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsViewModel.kt
+72
-4
app/src/main/java/com/isidroid/c23/ui/screen/details/component/PrintCodeComponent.kt
+49
-0
app/src/main/java/com/isidroid/c23/ui/screen/map/MapScreen.kt
+2
-1
app/src/main/java/com/isidroid/c23/ui/screen/map/_components/TPMapComponent.kt
+2
-5
app/src/main/java/com/isidroid/c23/ui/screen/render_preview/_component/PrintSizeModalComponent.kt
+1
-1
app/src/main/res/values/strings.xml
+3
-0
buildSrc/src/main/java/Versions.kt
+5
-5
ui/maps/build.gradle.kts
+1
-1
ui/maps/src/google/java/com/isidroid/ui/maps/MapsComponent.kt
+26
-4
ui/maps/src/google/java/com/isidroid/ui/maps/_MapComponent.kt
+0
-113
ui/maps/src/google/java/com/isidroid/ui/maps/data/mapper/MapMarkerMapper.kt
+2
-2
ui/maps/src/google/java/com/isidroid/ui/maps/model/MyClusterItem.kt
+3
-1
ui/maps/src/main/java/com/isidroid/ui/maps/ext/ExtGeo.kt
+11
-0
ui/maps/src/main/java/com/isidroid/ui/maps/model/MapMarker.kt
+10
-2
No files found.
app/src/main/java/com/isidroid/c23/Errors.kt
View file @
716c048f
...
...
@@ -2,4 +2,5 @@ package com.isidroid.c23
import
kotlin.jvm.Throws
class
SpotHasNoPrintProfilesException
(
m
:
String
?
=
null
):
Throwable
(
m
)
\ No newline at end of file
class
SpotHasNoPrintProfilesException
(
m
:
String
?
=
null
):
Throwable
(
m
)
class
JobNotFoundException
(
m
:
String
?
=
null
):
Throwable
(
m
)
\ No newline at end of file
app/src/main/java/com/isidroid/c23/constant/Argument.kt
View file @
716c048f
package
com.isidroid.c23.constant
import
androidx.annotation.StringDef
@Retention
(
AnnotationRetention
.
SOURCE
)
@StringDef
(
Argument
.
URI
,
Argument
.
LATITUDE
,
Argument
.
LONGITUDE
,
Argument
.
SPOT_CODE
,
Argument
.
ID
,
Argument
.
INFO
,
Argument
.
MARKER
)
annotation
class
Argument
{
companion
object
{
const
val
URI
=
"URI"
...
...
@@ -8,5 +11,7 @@ annotation class Argument {
const
val
LONGITUDE
=
"LONGITUDE"
const
val
SPOT_CODE
=
"SPOT_CODE"
const
val
ID
=
"ID"
const
val
INFO
=
"INFO"
const
val
MARKER
=
"MARKER"
}
}
app/src/main/java/com/isidroid/c23/data/mapper/PrintJobListItemMapper.kt
0 → 100644
View file @
716c048f
package
com.isidroid.c23.data.mapper
import
android.content.Context
import
com.isidroid.c23.domain.dto.PrintJobListItem
import
com.isidroid.c23.ext.getPrintJobStatus
import
com.isidroid.c23.ext.getPrintJobStatusColor
import
com.isidroid.job.model.PrintJob
import
com.isidroid.rendering.constant.printSizeName
import
com.isidroid.spot.model.RichSpot
import
java.io.File
fun
PrintJob
.
createListItem
(
context
:
Context
,
richSpot
:
RichSpot
?):
PrintJobListItem
{
val
profile
=
richSpot
?.
printProfiles
?.
find
{
it
.
id
==
profileId
}
return
PrintJobListItem
(
id
=
id
,
spotCode
=
richSpot
?.
spot
?.
code
,
spotName
=
richSpot
?.
spot
?.
name
?:
"Deleted spot"
,
cost
=
cost
,
paperInfo
=
printSize
.
printSizeName
,
isColor
=
profile
?.
grayscale
!=
true
,
statusColor
=
getPrintJobStatusColor
(
status
),
comment
=
comment
,
copies
=
copies
,
cover
=
sourceFiles
?.
firstOrNull
()
?.
takeIf
{
File
(
it
).
exists
()
},
statusName
=
context
.
getString
(
getPrintJobStatus
(
status
)),
accessCode
=
accessCode
.
orEmpty
(),
createdAt
=
createdAt
,
)
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/domain/use_case/DetailsUseCase.kt
0 → 100644
View file @
716c048f
package
com.isidroid.c23.domain.use_case
import
android.content.Context
import
com.isidroid.c23.JobNotFoundException
import
com.isidroid.c23.constant.Argument
import
com.isidroid.c23.data.mapper.createListItem
import
com.isidroid.c23.ext.isDebug
import
com.isidroid.core.FlowResult
import
com.isidroid.job.repository.JobRepository
import
com.isidroid.location.repository.LocationRepository
import
com.isidroid.spot.repository.SpotRepository
import
com.isidroid.ui.maps.ext.addKilometerToLatLng
import
com.isidroid.ui.maps.model.MapMarker
import
dagger.hilt.android.qualifiers.ApplicationContext
import
kotlinx.coroutines.flow.flow
import
javax.inject.Inject
import
javax.inject.Singleton
@Singleton
class
DetailsUseCase
@Inject
constructor
(
@ApplicationContext
private
val
context
:
Context
,
private
val
jobRepository
:
JobRepository
,
private
val
spotRepository
:
SpotRepository
,
private
val
locationRepository
:
LocationRepository
,
)
{
fun
loadDetails
(
id
:
String
)
=
flow
{
emit
(
FlowResult
.
Loading
)
val
job
=
jobRepository
.
readLocalList
(
ids
=
listOf
(
id
)).
firstOrNull
()
?:
throw
JobNotFoundException
()
val
richSpot
=
spotRepository
.
findLocalRichSpots
(
ids
=
listOf
(
job
.
spotId
))
?.
firstOrNull
()
val
result
=
job
.
createListItem
(
context
=
context
,
richSpot
=
richSpot
)
val
currentLocation
=
locationRepository
.
getCurrentLocation
()
val
location
=
if
(
isDebug
()
&&
richSpot
?.
spot
!=
null
)
addKilometerToLatLng
(
richSpot
.
spot
.
lat
,
richSpot
.
spot
.
lng
,
distance
=
0.2
)
else
currentLocation
val
mapMarker
=
richSpot
?.
spot
?.
let
{
spot
->
MapMarker
(
id
=
spot
.
id
,
lat
=
spot
.
lat
,
lng
=
spot
.
lng
,
name
=
spot
.
name
)
}
emit
(
FlowResult
.
Success
(
mapOf
(
Argument
.
INFO
to
result
,
Argument
.
LATITUDE
to
location
.
first
,
Argument
.
LONGITUDE
to
location
.
second
,
Argument
.
MARKER
to
mapMarker
)
)
)
}
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/domain/use_case/PrintJobsUseCase.kt
View file @
716c048f
package
com.isidroid.c23.domain.use_case
import
android.content.Context
import
com.isidroid.c23.domain.dto.PrintJobListItem
import
com.isidroid.c23.ext.getPrintJobStatus
import
com.isidroid.c23.ext.getPrintJobStatusColor
import
com.isidroid.c23.data.mapper.createListItem
import
com.isidroid.core.FlowResult
import
com.isidroid.job.repository.JobRepository
import
com.isidroid.rendering.constant.printSizeName
import
com.isidroid.spot.repository.SpotRepository
import
dagger.hilt.android.qualifiers.ApplicationContext
import
kotlinx.coroutines.flow.flow
import
java.io.File
import
javax.inject.Inject
import
javax.inject.Singleton
@Singleton
class
PrintJobsUseCase
@Inject
constructor
(
@ApplicationContext
private
val
context
:
Context
,
private
val
r
epository
:
JobRepository
,
private
val
jobR
epository
:
JobRepository
,
private
val
spotRepository
:
SpotRepository
)
{
fun
load
()
=
flow
{
emit
(
FlowResult
.
Loading
)
val
jobList
=
r
epository
.
readLocalList
()
val
jobList
=
jobR
epository
.
readLocalList
()
val
spots
=
jobList
.
map
{
it
.
spotId
}
.
distinct
()
.
let
{
spotRepository
.
findLocalRichSpots
(
it
)
}
?.
associateBy
({
it
.
spot
.
id
},
{
it
})
val
result
=
jobList
.
map
{
job
->
val
richSpot
=
spots
?.
get
(
job
.
spotId
)
val
profile
=
richSpot
?.
printProfiles
?.
find
{
it
.
id
==
job
.
profileId
}
PrintJobListItem
(
id
=
job
.
id
,
spotCode
=
richSpot
?.
spot
?.
code
,
spotName
=
richSpot
?.
spot
?.
name
?:
"Deleted spot"
,
cost
=
job
.
cost
,
paperInfo
=
job
.
printSize
.
printSizeName
,
isColor
=
profile
?.
grayscale
!=
true
,
statusColor
=
getPrintJobStatusColor
(
job
.
status
),
comment
=
job
.
comment
,
copies
=
job
.
copies
,
cover
=
job
.
sourceFiles
?.
firstOrNull
()
?.
takeIf
{
File
(
it
).
exists
()
},
statusName
=
context
.
getString
(
getPrintJobStatus
(
job
.
status
)),
accessCode
=
job
.
accessCode
.
orEmpty
(),
createdAt
=
job
.
createdAt
,
)
}
val
result
=
jobList
.
map
{
job
->
job
.
createListItem
(
context
=
context
,
richSpot
=
spots
?.
get
(
job
.
spotId
))
}
emit
(
FlowResult
.
Success
(
result
))
}
...
...
app/src/main/java/com/isidroid/c23/ui/navigation/destinations/JobDetailsDestination.kt
View file @
716c048f
package
com.isidroid.c23.ui.navigation.destinations
import
android.content.Context
import
android.content.Intent
import
android.widget.Toast
import
androidx.compose.runtime.Composable
import
androidx.compose.ui.platform.LocalContext
import
androidx.core.net.toUri
import
androidx.hilt.navigation.compose.hiltViewModel
import
androidx.navigation.NavHostController
import
com.isidroid.c23.ui.screen.details.JobDetailsContract
import
com.isidroid.c23.ui.screen.details.JobDetailsScreen
import
com.isidroid.c23.ui.screen.details.JobDetailsViewModel
import
com.isidroid.c23.ui.screen.print_jobs.PrintJobsContract
import
com.isidroid.c23.ui.screen.print_jobs.PrintJobsScreen
import
com.isidroid.c23.ui.screen.print_jobs.PrintJobsViewModel
@Composable
fun
JobDetailsDestination
(
navController
:
NavHostController
)
{
val
viewModel
:
JobDetailsViewModel
=
hiltViewModel
()
val
context
=
LocalContext
.
current
JobDetailsScreen
(
state
=
viewModel
.
viewState
,
effectFlow
=
viewModel
.
effect
,
spotResultStateFlow
=
viewModel
.
spotResultStateFlow
,
onEventSent
=
{
event
->
viewModel
.
setEvent
(
event
)
},
onNavigationRequested
=
{
effect
->
when
(
effect
)
{
JobDetailsContract
.
Effect
.
Navigation
.
ToBack
->
navController
.
popBackStack
()
is
JobDetailsContract
.
Effect
.
Navigation
.
ToNavigationApp
->
context
.
openGoogleMaps
(
lat
=
effect
.
lat
,
lng
=
effect
.
lng
)
}
},
)
}
private
fun
Context
.
openGoogleMaps
(
lat
:
Double
?,
lng
:
Double
?)
{
if
(
lat
==
null
||
lng
==
null
)
return
val
gmmIntentUri
=
"geo:$lat,$lng"
.
toUri
()
val
mapIntent
=
Intent
(
Intent
.
ACTION_VIEW
,
gmmIntentUri
)
// mapIntent.setPackage("com.google.android.apps.maps")
if
(
mapIntent
.
resolveActivity
(
packageManager
)
!=
null
)
{
startActivity
(
mapIntent
)
}
else
{
Toast
.
makeText
(
this
,
"Navigation app not found"
,
Toast
.
LENGTH_LONG
).
show
()
}
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsContract.kt
View file @
716c048f
package
com.isidroid.c23.ui.screen.details
import
com.isidroid.c23.
ui.screen.content.ContentContract
import
com.isidroid.c23.
domain.dto.PrintJobListItem
import
com.isidroid.core.vm.ViewEvent
import
com.isidroid.core.vm.ViewSideEffect
import
com.isidroid.core.vm.ViewState
...
...
@@ -8,13 +8,23 @@ import com.isidroid.core.vm.ViewState
class
JobDetailsContract
{
sealed
interface
Event
:
ViewEvent
{
data
object
ToBack
:
Event
data
object
OpenConfirmationMapRoute
:
Event
data
object
DismissBuildRouteConfirmation
:
Event
data
object
OpenNavigationApp
:
Event
}
sealed
interface
Effect
:
ViewSideEffect
{
sealed
interface
Navigation
:
Effect
{
data
object
ToBack
:
Navigation
data class
ToNavigationApp
(
val
lat
:
Double
?,
val
lng
:
Double
?)
:
Navigation
}
}
data class
State
(
val
i
:
Int
=
0
)
:
ViewState
data class
State
(
val
isLoading
:
Boolean
=
false
,
val
printJob
:
PrintJobListItem
?
=
null
,
val
lat
:
Double
?
=
null
,
val
lng
:
Double
?
=
null
,
val
routeConfirmationVisible
:
Boolean
=
false
)
:
ViewState
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsScreen.kt
View file @
716c048f
...
...
@@ -2,37 +2,73 @@ package com.isidroid.c23.ui.screen.details
import
androidx.activity.compose.BackHandler
import
androidx.compose.foundation.background
import
androidx.compose.foundation.clickable
import
androidx.compose.foundation.layout.Box
import
androidx.compose.foundation.layout.Column
import
androidx.compose.foundation.layout.WindowInsets
import
androidx.compose.foundation.layout.consumeWindowInsets
import
androidx.compose.foundation.layout.displayCutout
import
androidx.compose.foundation.layout.fillMaxSize
import
androidx.compose.foundation.layout.fillMaxWidth
import
androidx.compose.foundation.layout.ime
import
androidx.compose.foundation.layout.navigationBars
import
androidx.compose.foundation.layout.padding
import
androidx.compose.foundation.layout.statusBars
import
androidx.compose.foundation.shape.RoundedCornerShape
import
androidx.compose.material.Button
import
androidx.compose.material.Icon
import
androidx.compose.material.icons.Icons
import
androidx.compose.material.icons.rounded.Close
import
androidx.compose.material3.BottomSheetDefaults
import
androidx.compose.material3.Card
import
androidx.compose.material3.CardDefaults
import
androidx.compose.material3.ExperimentalMaterial3Api
import
androidx.compose.material3.MaterialTheme
import
androidx.compose.material3.ModalBottomSheet
import
androidx.compose.material3.Scaffold
import
androidx.compose.material3.Surface
import
androidx.compose.material3.Text
import
androidx.compose.material3.TextButton
import
androidx.compose.material3.TopAppBar
import
androidx.compose.material3.TopAppBarDefaults
import
androidx.compose.material3.rememberModalBottomSheetState
import
androidx.compose.runtime.Composable
import
androidx.compose.runtime.LaunchedEffect
import
androidx.compose.runtime.State
import
androidx.compose.runtime.mutableStateOf
import
androidx.compose.runtime.remember
import
androidx.compose.runtime.getValue
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.res.stringResource
import
androidx.compose.ui.tooling.preview.Devices
import
androidx.compose.ui.tooling.preview.Preview
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.sp
import
androidx.constraintlayout.compose.ConstraintLayout
import
androidx.constraintlayout.compose.Dimension
import
com.airbnb.lottie.compose.LottieAnimation
import
com.airbnb.lottie.compose.LottieCompositionSpec
import
com.airbnb.lottie.compose.LottieConstants
import
com.airbnb.lottie.compose.rememberLottieComposition
import
com.isidroid.c23.R
import
com.isidroid.c23.ui._component.TopAppBarComponent
import
com.isidroid.c23.ui.screen.details.component.PrintCodeComponent
import
com.isidroid.c23.ui.screen.map.MapContract
import
com.isidroid.c23.ui.screen.map._components.TPMapComponent
import
com.isidroid.c23.ui.theme.AppTheme
import
com.isidroid.core.vm.SIDE_EFFECTS_KEY
import
com.isidroid.ui.maps.model.MapMarker
import
kotlinx.coroutines.flow.Flow
import
kotlinx.coroutines.flow.collect
import
kotlinx.coroutines.flow.StateFlow
import
timber.log.Timber
@OptIn
(
ExperimentalMaterial3Api
::
class
)
@Composable
fun
JobDetailsScreen
(
state
:
State
<
JobDetailsContract
.
State
>,
effectFlow
:
Flow
<
JobDetailsContract
.
Effect
>?,
spotResultStateFlow
:
StateFlow
<
List
<
MapMarker
>>,
onEventSent
:
(
event
:
JobDetailsContract
.
Event
)
->
Unit
,
onNavigationRequested
:
(
navigationEffect
:
JobDetailsContract
.
Effect
.
Navigation
)
->
Unit
,
modifier
:
Modifier
=
Modifier
,
...
...
@@ -47,12 +83,11 @@ fun JobDetailsScreen(
BackHandler
{
onEventSent
(
JobDetailsContract
.
Event
.
ToBack
)
}
Scaffold
(
modifier
=
modifier
.
fillMaxSize
(),
topBar
=
{
TopAppBarComponent
(
text
=
"Print job details"
,
text
=
stringResource
(
id
=
R
.
string
.
print_job_details
)
,
colors
=
TopAppBarDefaults
.
topAppBarColors
(),
onNavigationClick
=
{
onEventSent
(
JobDetailsContract
.
Event
.
ToBack
)
}
)
...
...
@@ -60,11 +95,131 @@ fun JobDetailsScreen(
)
{
paddingValues
->
Box
(
modifier
=
Modifier
.
padding
(
paddingValues
)
.
consumeWindowInsets
(
paddingValues
)
.
padding
(
top
=
paddingValues
.
calculateTopPadding
())
.
fillMaxSize
(),
contentAlignment
=
Alignment
.
Center
contentAlignment
=
Alignment
.
TopStart
)
{
Text
(
"Screen is under construction"
)
when
{
state
.
value
.
isLoading
->
LoadingComponent
()
}
InformationContent
(
state
,
spotResultStateFlow
,
onEventSent
)
DisplayMapRouteConfirmation
(
state
,
onEventSent
)
}
}
}
@Composable
private
fun
LoadingComponent
(
modifier
:
Modifier
=
Modifier
)
{
val
composition
by
rememberLottieComposition
(
LottieCompositionSpec
.
RawRes
(
R
.
raw
.
startapp
))
LottieAnimation
(
composition
=
composition
,
iterations
=
LottieConstants
.
IterateForever
,
modifier
=
modifier
.
fillMaxSize
()
.
padding
(
48
.
dp
)
)
}
@Composable
private
fun
InformationContent
(
state
:
State
<
JobDetailsContract
.
State
>,
spotResultStateFlow
:
StateFlow
<
List
<
MapMarker
>>,
onEventSent
:
(
event
:
JobDetailsContract
.
Event
)
->
Unit
,
modifier
:
Modifier
=
Modifier
)
{
val
information
=
state
.
value
.
printJob
?:
return
ConstraintLayout
(
modifier
=
modifier
.
fillMaxSize
())
{
val
(
codeView
,
statusView
,
mapView
)
=
createRefs
()
PrintCodeComponent
(
code
=
information
.
accessCode
,
modifier
=
Modifier
.
constrainAs
(
codeView
)
{
top
.
linkTo
(
parent
.
top
)
linkTo
(
start
=
parent
.
start
,
end
=
parent
.
end
)
}
)
Text
(
text
=
information
.
statusName
,
fontSize
=
14
.
sp
,
fontWeight
=
FontWeight
.
W200
,
letterSpacing
=
.
76
.
sp
,
color
=
Color
.
White
,
modifier
=
Modifier
.
clip
(
RoundedCornerShape
(
6
.
dp
))
.
background
(
information
.
statusColor
)
.
padding
(
horizontal
=
6
.
dp
,
vertical
=
2
.
dp
)
.
constrainAs
(
statusView
)
{
top
.
linkTo
(
codeView
.
bottom
,
12
.
dp
)
end
.
linkTo
(
parent
.
end
,
12
.
dp
)
}
)
Card
(
modifier
=
Modifier
.
fillMaxWidth
()
.
padding
(
top
=
12
.
dp
)
.
constrainAs
(
mapView
)
{
linkTo
(
top
=
statusView
.
bottom
,
bottom
=
parent
.
bottom
)
height
=
Dimension
.
fillToConstraints
},
shape
=
RoundedCornerShape
(
topStart
=
16
.
dp
,
topEnd
=
16
.
dp
,
bottomStart
=
0
.
dp
,
bottomEnd
=
0
.
dp
),
elevation
=
CardDefaults
.
cardElevation
(
defaultElevation
=
12
.
dp
)
)
{
TPMapComponent
(
modifier
=
Modifier
,
onEventSent
=
{
event
->
when
(
event
)
{
is
MapContract
.
Event
.
ClickOnMarker
->
onEventSent
(
JobDetailsContract
.
Event
.
OpenConfirmationMapRoute
)
else
->
{}
}
},
mapMarkersStateFlow
=
spotResultStateFlow
,
lat
=
state
.
value
.
lat
,
lng
=
state
.
value
.
lng
)
}
}
}
@OptIn
(
ExperimentalMaterial3Api
::
class
)
@Composable
private
fun
DisplayMapRouteConfirmation
(
state
:
State
<
JobDetailsContract
.
State
>,
onEventSent
:
(
event
:
JobDetailsContract
.
Event
)
->
Unit
)
{
if
(!
state
.
value
.
routeConfirmationVisible
)
return
val
sheetState
=
rememberModalBottomSheetState
()
ModalBottomSheet
(
onDismissRequest
=
{
onEventSent
(
JobDetailsContract
.
Event
.
DismissBuildRouteConfirmation
)
},
sheetState
=
sheetState
,
windowInsets
=
WindowInsets
.
statusBars
,
modifier
=
Modifier
.
fillMaxWidth
()
.
padding
(
bottom
=
120
.
dp
),
)
{
Text
(
text
=
stringResource
(
id
=
R
.
string
.
app_navigation_explanation
),
modifier
=
Modifier
.
padding
(
horizontal
=
16
.
dp
),
textAlign
=
TextAlign
.
Center
)
TextButton
(
onClick
=
{
onEventSent
(
JobDetailsContract
.
Event
.
OpenNavigationApp
)
},
modifier
=
Modifier
.
fillMaxWidth
()
.
padding
(
horizontal
=
12
.
dp
,
vertical
=
18
.
dp
)
)
{
Text
(
text
=
"Open in Google Maps"
,
)
}
}
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/ui/screen/details/JobDetailsViewModel.kt
View file @
716c048f
package
com.isidroid.c23.ui.screen.details
import
androidx.compose.material.icons.Icons
import
androidx.compose.material.icons.outlined.AccountCircle
import
androidx.lifecycle.SavedStateHandle
import
androidx.lifecycle.viewModelScope
import
com.isidroid.c23.constant.Argument
import
com.isidroid.c23.domain.dto.PrintJobListItem
import
com.isidroid.c23.domain.use_case.DetailsUseCase
import
com.isidroid.c23.ext.isDebug
import
com.isidroid.core.FlowResult
import
com.isidroid.core.vm.BaseViewModel
import
com.isidroid.ui.maps.model.MapMarker
import
com.isidroid.utils.catchTimber
import
dagger.hilt.android.lifecycle.HiltViewModel
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.flow.MutableStateFlow
import
kotlinx.coroutines.flow.asStateFlow
import
kotlinx.coroutines.flow.filterNotNull
import
kotlinx.coroutines.flow.firstOrNull
import
kotlinx.coroutines.flow.flowOn
import
kotlinx.coroutines.flow.onEach
import
kotlinx.coroutines.launch
import
javax.inject.Inject
@HiltViewModel
class
JobDetailsViewModel
@Inject
constructor
()
:
BaseViewModel
<
JobDetailsContract
.
Event
,
JobDetailsContract
.
State
,
JobDetailsContract
.
Effect
>()
{
class
JobDetailsViewModel
@Inject
constructor
(
private
val
useCase
:
DetailsUseCase
,
savedStateHandle
:
SavedStateHandle
)
:
BaseViewModel
<
JobDetailsContract
.
Event
,
JobDetailsContract
.
State
,
JobDetailsContract
.
Effect
>()
{
private
val
_spotResultStateFlow
=
MutableStateFlow
<
List
<
MapMarker
>>(
emptyList
())
val
spotResultStateFlow
=
_spotResultStateFlow
.
asStateFlow
()
init
{
viewModelScope
.
launch
{
savedStateHandle
.
getStateFlow
<
String
?>(
Argument
.
ID
,
null
)
.
filterNotNull
()
.
onEach
{
loadDetails
(
it
)
}
.
firstOrNull
()
}
}
override
val
isDebug
:
Boolean
=
isDebug
()
override
fun
setInitialState
():
JobDetailsContract
.
State
=
JobDetailsContract
.
State
()
override
suspend
fun
handleEvents
(
event
:
JobDetailsContract
.
Event
)
{
when
(
event
){
JobDetailsContract
.
Event
.
ToBack
->
setEffect
{
JobDetailsContract
.
Effect
.
Navigation
.
ToBack
}
}
when
(
event
)
{
JobDetailsContract
.
Event
.
ToBack
->
setEffect
{
JobDetailsContract
.
Effect
.
Navigation
.
ToBack
}
JobDetailsContract
.
Event
.
OpenConfirmationMapRoute
->
setState
{
copy
(
routeConfirmationVisible
=
true
)
}
JobDetailsContract
.
Event
.
DismissBuildRouteConfirmation
->
setState
{
copy
(
routeConfirmationVisible
=
false
)
}
JobDetailsContract
.
Event
.
OpenNavigationApp
->
openNavigationApp
()
}
}
// handle events
private
suspend
fun
loadDetails
(
id
:
String
)
{
useCase
.
loadDetails
(
id
)
.
flowOn
(
Dispatchers
.
IO
)
.
catchTimber
{
}
.
collect
{
res
->
when
(
res
)
{
FlowResult
.
Loading
->
setState
{
copy
(
isLoading
=
true
)
}
is
FlowResult
.
Success
->
onDetails
(
item
=
res
.
result
[
Argument
.
INFO
]
as
?
PrintJobListItem
,
lat
=
res
.
result
[
Argument
.
LATITUDE
]
as
?
Double
,
lng
=
res
.
result
[
Argument
.
LONGITUDE
]
as
?
Double
,
mapMarker
=
res
.
result
[
Argument
.
MARKER
]
as
?
MapMarker
)
}
}
}
private
suspend
fun
onDetails
(
item
:
PrintJobListItem
?,
lat
:
Double
?,
lng
:
Double
?,
mapMarker
:
MapMarker
?)
{
val
myLocation
=
MapMarker
(
id
=
"my_location"
,
lat
=
lat
?:
0.0
,
lng
=
lng
?:
0.0
,
name
=
"My location"
,
imageVector
=
Icons
.
Outlined
.
AccountCircle
)
_spotResultStateFlow
.
emit
(
listOfNotNull
(
mapMarker
,
myLocation
))
setState
{
copy
(
isLoading
=
false
,
printJob
=
item
,
lat
=
lat
,
lng
=
lng
)
}
}
private
fun
openNavigationApp
(){
setState
{
copy
(
routeConfirmationVisible
=
false
)
}
setEffect
{
JobDetailsContract
.
Effect
.
Navigation
.
ToNavigationApp
(
viewState
.
value
.
lat
,
viewState
.
value
.
lng
)
}
}
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/ui/screen/details/component/PrintCodeComponent.kt
0 → 100644
View file @
716c048f
package
com.isidroid.c23.ui.screen.details.component
import
androidx.compose.foundation.background
import
androidx.compose.foundation.layout.Row
import
androidx.compose.foundation.layout.padding
import
androidx.compose.foundation.shape.RoundedCornerShape
import
androidx.compose.material3.MaterialTheme
import
androidx.compose.material3.Surface
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.tooling.preview.Preview
import
androidx.compose.ui.unit.dp
import
androidx.compose.ui.unit.sp
@Composable
fun
PrintCodeComponent
(
code
:
String
,
modifier
:
Modifier
=
Modifier
)
{
Row
(
modifier
=
modifier
)
{
for
(
c
in
code
)
PrintCodeElement
(
c
)
}
}
@Composable
private
fun
PrintCodeElement
(
c
:
Char
)
{
Text
(
text
=
"$c"
,
fontSize
=
24
.
sp
,
color
=
Color
.
White
,
style
=
MaterialTheme
.
typography
.
displayLarge
,
modifier
=
Modifier
.
padding
(
horizontal
=
8
.
dp
,
vertical
=
8
.
dp
)
.
background
(
Color
.
Blue
,
RoundedCornerShape
(
6
.
dp
))
.
padding
(
horizontal
=
12
.
dp
,
vertical
=
0
.
dp
)
)
}
@Preview
@Composable
fun
PrintCodeComponentPreview
()
{
Surface
{
PrintCodeComponent
(
"7819128"
)
}
}
\ No newline at end of file
app/src/main/java/com/isidroid/c23/ui/screen/map/MapScreen.kt
View file @
716c048f
...
...
@@ -68,7 +68,8 @@ fun MapScreen(
}
)
{
paddingValues
->
TPMapComponent
(
state
=
state
,
lat
=
state
.
value
.
lat
,
lng
=
state
.
value
.
lng
,
modifier
=
Modifier
.
fillMaxSize
()
.
consumeWindowInsets
(
paddingValues
),
...
...
app/src/main/java/com/isidroid/c23/ui/screen/map/_components/TPMapComponent.kt
View file @
716c048f
package
com.isidroid.c23.ui.screen.map._components
import
androidx.compose.runtime.Composable
import
androidx.compose.runtime.State
import
androidx.compose.ui.Modifier
import
com.isidroid.c23.ui.screen.map.MapContract
import
com.isidroid.ui.maps.model.MapMarker
...
...
@@ -11,13 +10,11 @@ import kotlinx.coroutines.flow.StateFlow
@Composable
fun
TPMapComponent
(
onEventSent
:
(
event
:
MapContract
.
Event
)
->
Unit
,
state
:
State
<
MapContract
.
State
>,
lat
:
Double
?,
lng
:
Double
?,
mapMarkersStateFlow
:
StateFlow
<
List
<
MapMarker
>>,
modifier
:
Modifier
=
Modifier
)
{
val
lat
=
state
.
value
.
lat
val
lng
=
state
.
value
.
lng
if
(
lat
==
null
||
lng
==
null
)
return
...
...
app/src/main/java/com/isidroid/c23/ui/screen/render_preview/_component/PrintSizeModalComponent.kt
View file @
716c048f
...
...
@@ -148,7 +148,7 @@ private fun OptionsModalComponent(
sheetState
=
sheetState
,
modifier
=
modifier
.
fillMaxWidth
(),
)
{
Column
(
modifier
=
m
odifier
.
fillMaxWidth
())
{
Column
(
modifier
=
M
odifier
.
fillMaxWidth
())
{
TopAppBar
(
title
=
{
Text
(
text
=
stringResource
(
id
=
title
))
},
navigationIcon
=
{
Icon
(
Icons
.
Rounded
.
Close
,
contentDescription
=
null
,
modifier
=
Modifier
.
clickable
{
onCancel
()
})
},
...
...
app/src/main/res/values/strings.xml
View file @
716c048f
...
...
@@ -38,4 +38,6 @@
<string
name=
"print_job_list"
>
My Print jobs
</string>
<string
name=
"rendered_files_copied_message"
>
Successfully copied %d files
</string>
<string
name=
"empty"
/>
<string
name=
"print_job_details"
>
Job details
</string>
<string
name=
"app_navigation_explanation"
>
To navigate to this location, please use an external navigation app. Tap the button below to open your preferred navigation app and plan your route.
</string>
</resources>
\ No newline at end of file
buildSrc/src/main/java/Versions.kt
View file @
716c048f
...
...
@@ -4,7 +4,7 @@ object BuildVersions {
const
val
TARGET_SDK
=
34
const
val
KOTLIN_COMPILER_EXT_VERSION
=
"1.5.3"
const
val
ANDROID_PLUGIN
=
"8.5.
0
"
const
val
ANDROID_PLUGIN
=
"8.5.
1
"
const
val
KOTLIN
=
"1.9.10"
const
val
KSP
=
"1.9.10-1.0.13"
}
...
...
@@ -21,7 +21,7 @@ object GoogleVersions {
const
val
hilt
=
"2.49"
const
val
hiltNavigationCompose
=
"1.2.0"
const
val
hiltWork
=
"1.0.0"
const
val
lifecycle
=
"2.8.
2
"
const
val
lifecycle
=
"2.8.
3
"
const
val
navigation
=
"2.7.0"
const
val
navigationCompose
=
"2.7.7"
const
val
preferences
=
"1.2.1"
...
...
@@ -29,7 +29,7 @@ object GoogleVersions {
const
val
roomCompiler
=
"2.5.1"
const
val
splash
=
"1.0.1"
const
val
work
=
"2.9.0"
const
val
services
=
"4.4.
1
"
const
val
services
=
"4.4.
2
"
const
val
constraint
=
"1.0.1"
const
val
paging
=
"3.2.1"
const
val
materialView
=
"1.11.0"
...
...
@@ -51,14 +51,14 @@ object NetworkVersions {
object
FirebaseDependencies
{
const
val
analytics
=
"com.google.firebase:firebase-analytics-ktx"
const
val
bom
=
"33.1.
1
"
const
val
bom
=
"33.1.
2
"
const
val
crashlytics
=
"com.google.firebase:firebase-crashlytics-ktx"
const
val
messaging
=
"com.google.firebase:firebase-messaging-ktx"
const
val
config
=
"com.google.firebase:firebase-config-ktx"
}
object
FirebaseVersions
{
const
val
crashlyticsPlugin
=
"
2.9.9
"
const
val
crashlyticsPlugin
=
"
3.0.2
"
}
object
ToolsVersions
{
...
...
ui/maps/build.gradle.kts
View file @
716c048f
...
...
@@ -78,7 +78,7 @@ dependencies {
googleImplementation("com.google.maps.android:maps-compose:${GoogleVersions.maps}")
googleImplementation("com.google.maps.android:android-maps-utils:${GoogleVersions.mapUtils}")
googleImplementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.
1
")
googleImplementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.
3
")
googleImplementation("com.google.maps.android:maps-compose-utils:2.11.4")
googleImplementation("com.google.maps.android:maps-compose-widgets:2.11.4")
...
...
ui/maps/src/google/java/com/isidroid/ui/maps/MapsComponent.kt
View file @
716c048f
package
com.isidroid.ui.maps
import
androidx.compose.foundation.Image
import
androidx.compose.foundation.background
import
androidx.compose.foundation.layout.Box
import
androidx.compose.foundation.layout.fillMaxSize
import
androidx.compose.foundation.layout.height
import
androidx.compose.foundation.layout.padding
import
androidx.compose.foundation.layout.width
import
androidx.compose.foundation.shape.CircleShape
import
androidx.compose.material.icons.Icons
import
androidx.compose.material.icons.filled.LocationOn
import
androidx.compose.material.icons.filled.Place
import
androidx.compose.material.icons.outlined.MailOutline
import
androidx.compose.material.icons.rounded.LocationOn
import
androidx.compose.material3.Icon
import
androidx.compose.material3.IconButton
import
androidx.compose.runtime.Composable
...
...
@@ -16,13 +22,16 @@ import androidx.compose.runtime.remember
import
androidx.compose.runtime.snapshotFlow
import
androidx.compose.ui.Alignment
import
androidx.compose.ui.Modifier
import
androidx.compose.ui.draw.scale
import
androidx.compose.ui.graphics.Color
import
androidx.compose.ui.platform.LocalContext
import
androidx.compose.ui.platform.LocalLifecycleOwner
import
androidx.compose.ui.unit.dp
import
androidx.lifecycle.flowWithLifecycle
import
com.google.android.gms.maps.CameraUpdateFactory
import
com.google.android.gms.maps.model.CameraPosition
import
com.google.android.gms.maps.model.LatLng
import
com.google.maps.android.clustering.ClusterManager
import
com.google.maps.android.compose.GoogleMap
import
com.google.maps.android.compose.MapProperties
import
com.google.maps.android.compose.MapUiSettings
...
...
@@ -53,6 +62,7 @@ fun MapsComponent(
val
items
=
mapMarkersStateFlow
.
collectAsState
()
val
stateFlow
=
remember
{
MutableStateFlow
(
LatLng
(
lat
,
lng
))
}
val
lifecycleOwner
=
LocalLifecycleOwner
.
current
val
context
=
LocalContext
.
current
LaunchedEffect
(
cameraPositionState
)
{
snapshotFlow
{
cameraPositionState
.
position
}
...
...
@@ -79,12 +89,13 @@ fun MapsComponent(
modifier
=
modifier
.
fillMaxSize
(),
properties
=
MapProperties
(
isMyLocationEnabled
=
false
),
cameraPositionState
=
cameraPositionState
,
uiSettings
=
MapUiSettings
(
zoomControlsEnabled
=
false
),
uiSettings
=
MapUiSettings
(
zoomControlsEnabled
=
true
),
onMapLoaded
=
{},
onMapClick
=
{},
onMyLocationClick
=
{},
)
{
val
parkMarkers
=
items
.
value
.
map
{
it
.
transformToClusterItem
()
}
Clustering
(
items
=
parkMarkers
,
onClusterItemClick
=
{
...
...
@@ -96,15 +107,26 @@ fun MapsComponent(
true
},
onClusterItemInfoWindowClick
=
{},
clusterItemContent
=
{
if
(
it
.
imageVector
!=
null
)
Icon
(
it
.
imageVector
,
contentDescription
=
null
)
else
Icon
(
Icons
.
Default
.
Place
,
contentDescription
=
null
,
tint
=
Color
.
Red
,
modifier
=
Modifier
.
scale
(
1.2f
)
)
}
)
}
IconButton
(
onClick
=
{
moveCameraToMyLocation
()
},
modifier
=
Modifier
.
align
(
Alignment
.
Bottom
End
)
.
padding
(
bottom
=
72
.
dp
,
end
=
12
.
dp
)
.
background
(
Color
.
Black
,
CircleShape
)
.
align
(
Alignment
.
Bottom
Start
)
.
padding
(
bottom
=
72
.
dp
,
start
=
12
.
dp
)
.
background
(
Color
.
Black
.
copy
(
alpha
=
.
45f
)
,
CircleShape
)
)
{
Icon
(
imageVector
=
Icons
.
Default
.
LocationOn
,
...
...
ui/maps/src/google/java/com/isidroid/ui/maps/_MapComponent.kt
deleted
100644 → 0
View file @
564e344c
package
com.isidroid.ui.maps
// val context = LocalContext.current
// val lifecycleOwner = LocalLifecycleOwner.current
//
// val stateFlow = remember { MutableStateFlow(LatLng(lat, lng)) }
// var googleMapView by remember { mutableStateOf<GoogleMap?>(null) }
// var clusterManager by remember { mutableStateOf<ClusterManager<MyClusterItem>?>(null) }
//
//
// LaunchedEffect(stateFlow) {
// stateFlow
// .flowWithLifecycle(lifecycleOwner.lifecycle)
// .distinctUntilChanged()
// .debounce(1000L)
// .collect { newLatLng ->
// val radius = findMapRadius(googleMapView)
// onCameraMove(newLatLng.latitude, newLatLng.longitude, radius)
// }
// }
//
// MapCreatorComponent(
// modifier = modifier,
// stateFlow = stateFlow,
// onMapViewReady = { map, manager ->
// googleMapView = map
// clusterManager = manager
//
// Timber.i("sdfsdfsdf MapCreatorComponent.onMapViewReady")
//
//// map.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(lat, lng), 16f))
// }
// )
//
// HandleCallbacks(mapMarkersStateFlow, clusterManager)
//
// LaunchedEffect(stateFlow) {
// stateFlow
// .flowWithLifecycle(lifecycleOwner.lifecycle)
// .distinctUntilChanged()
// .debounce(1000L)
// .collect { newLatLng ->
// val radius = findMapRadius(googleMapView)
// onCameraMove(newLatLng.latitude, newLatLng.longitude, radius)
// }
// }
//}
//
//@Composable
//private fun HandleCallbacks(mapMarkersStateFlow: StateFlow<List<MapMarker>>, clusterManager: ClusterManager<MyClusterItem>?) {
//// val coroutineScope = rememberCoroutineScope()
// val mapMarkers = mapMarkersStateFlow.collectAsState()
////
//// Timber.i("sdfsdfsdf mapMarkers=${mapMarkers.value.size}, clusterManager=$clusterManager")
//}
//
//@Composable
//private fun MapCreatorComponent(
// modifier: Modifier,
// stateFlow: MutableStateFlow<LatLng>,
// onMapViewReady: (GoogleMap, ClusterManager<MyClusterItem>) -> Unit
//) {
// val mapView = rememberMapViewWithLifecycle()
// val context = LocalContext.current
// val coroutineScope = rememberCoroutineScope()
// var isCreated by remember { mutableStateOf(false) }
//
// Timber.i("sdfsdfsdf MapCreatorComponent create isCreated=$isCreated")
//
// if(isCreated) return
//
// AndroidView(
// factory = { mapView },
// modifier = modifier
// ) {
// mapView.getMapAsync { googleMap ->
// Timber.i("sdfsdfsdf MapCreatorComponent.onMapViewReady")
// isCreated = true
//
//
// val clusterManager = ClusterManager<MyClusterItem>(context, googleMap)
// onMapViewReady(googleMap, clusterManager)
//
// googleMap.setOnCameraIdleListener(clusterManager)
// googleMap.setOnMarkerClickListener(clusterManager)
//
// clusterManager.setOnClusterClickListener { clusterItem ->
// true
// }
//
// clusterManager.setOnClusterItemClickListener { clusterItem -> true }
// googleMap.setOnCameraMoveListener {
// val centerLocation = googleMap.cameraPosition.target
// coroutineScope.launch { stateFlow.emit(centerLocation) }
// }
// }
// }
//}
//
//
////private fun addClusteredMarkers(clusterManager: ClusterManager<MyClusterItem>) {
//// val items = listOf(
//// MyClusterItem(LatLng(40.7579247, -73.9881229), "Title 1", "Snippet 1"),
//// MyClusterItem(LatLng(40.7579347, -73.9881129), "Title 2", "Snippet 2"),
//// MyClusterItem(LatLng(40.7579347, -73.9851129), "Title 3", "Snippet 2")
////
//// )
////
//// clusterManager.clearItems()
//// clusterManager.addItems(items)
//// clusterManager.cluster()
////}
\ No newline at end of file
ui/maps/src/google/java/com/isidroid/ui/maps/data/mapper/MapMarkerMapper.kt
View file @
716c048f
...
...
@@ -4,4 +4,4 @@ import com.google.android.gms.maps.model.LatLng
import
com.isidroid.ui.maps.model.MapMarker
import
com.isidroid.ui.maps.model.MyClusterItem
fun
MapMarker
.
transformToClusterItem
()
=
MyClusterItem
(
latLng
=
LatLng
(
lat
,
lng
),
title
=
name
,
snippet
=
""
,
id
=
id
)
\ No newline at end of file
fun
MapMarker
.
transformToClusterItem
()
=
MyClusterItem
(
latLng
=
LatLng
(
lat
,
lng
),
title
=
name
,
snippet
=
""
,
id
=
id
,
imageVector
=
imageVector
)
\ No newline at end of file
ui/maps/src/google/java/com/isidroid/ui/maps/model/MyClusterItem.kt
View file @
716c048f
package
com.isidroid.ui.maps.model
import
androidx.compose.ui.graphics.vector.ImageVector
import
com.google.android.gms.maps.model.LatLng
import
com.google.maps.android.clustering.ClusterItem
...
...
@@ -7,7 +8,8 @@ data class MyClusterItem(
val
id
:
String
,
private
val
latLng
:
LatLng
,
private
val
title
:
String
,
private
val
snippet
:
String
private
val
snippet
:
String
,
val
imageVector
:
ImageVector
?
=
null
)
:
ClusterItem
{
override
fun
getPosition
():
LatLng
=
latLng
override
fun
getTitle
():
String
=
title
...
...
ui/maps/src/main/java/com/isidroid/ui/maps/ext/ExtGeo.kt
0 → 100644
View file @
716c048f
package
com.isidroid.ui.maps.ext
// Константы
const
val
EARTH_RADIUS
=
6371.0
// Радиус Земли в километрах
fun
addKilometerToLatLng
(
lat
:
Double
,
lng
:
Double
,
distance
:
Double
=
1.0
):
Pair
<
Double
,
Double
>
{
val
latInRadians
=
Math
.
toRadians
(
lat
)
val
newLat
=
latInRadians
+
distance
/
EARTH_RADIUS
val
newLatInDegrees
=
Math
.
toDegrees
(
newLat
)
return
Pair
(
newLatInDegrees
,
lng
)
}
ui/maps/src/main/java/com/isidroid/ui/maps/model/MapMarker.kt
View file @
716c048f
package
com.isidroid.ui.maps.model
data class
MapMarker
(
val
id
:
String
,
val
name
:
String
,
val
lat
:
Double
,
val
lng
:
Double
)
\ No newline at end of file
import
androidx.compose.ui.graphics.vector.ImageVector
data class
MapMarker
(
val
id
:
String
,
val
name
:
String
,
val
lat
:
Double
,
val
lng
:
Double
,
val
imageVector
:
ImageVector
?
=
null
)
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment