Redesign places result
This commit is contained in:
parent
a382c40c20
commit
2cda2437f3
@ -2,72 +2,75 @@ package de.mm20.launcher2.ui.launcher.search.location
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.EaseOutBack
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.animateValueAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandIn
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.ArrowUpward
|
||||
import androidx.compose.material.icons.automirrored.rounded.NavigateNext
|
||||
import androidx.compose.material.icons.rounded.BugReport
|
||||
import androidx.compose.material.icons.rounded.FiberManualRecord
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Map
|
||||
import androidx.compose.material.icons.rounded.Navigation
|
||||
import androidx.compose.material.icons.rounded.Phone
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material.icons.rounded.TravelExplore
|
||||
import androidx.compose.material.icons.twotone.CloudOff
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.AssistChipDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.roundToIntRect
|
||||
import androidx.compose.ui.unit.times
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import de.mm20.launcher2.i18n.R
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.Location
|
||||
import de.mm20.launcher2.ui.animation.animateHorizontalAlignmentAsState
|
||||
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
|
||||
import de.mm20.launcher2.search.OpeningHours
|
||||
import de.mm20.launcher2.search.OpeningSchedule
|
||||
import de.mm20.launcher2.ui.component.DefaultToolbarAction
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.Toolbar
|
||||
import de.mm20.launcher2.ui.component.ToolbarAction
|
||||
import de.mm20.launcher2.ui.ktx.DegreesConverter
|
||||
import de.mm20.launcher2.ui.ktx.metersToLocalizedString
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
||||
@ -76,14 +79,11 @@ import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
|
||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||
import de.mm20.launcher2.ui.modifier.scale
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.DayOfWeek
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.time.format.TextStyle
|
||||
import java.util.Locale
|
||||
import kotlin.math.pow
|
||||
|
||||
@Composable
|
||||
@ -117,13 +117,442 @@ fun LocationItem(
|
||||
} else emptyFlow()
|
||||
}.collectAsStateWithLifecycle(null)
|
||||
|
||||
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
||||
val badge by viewModel.badge.collectAsStateWithLifecycle(null)
|
||||
|
||||
val imperialUnits by viewModel.imperialUnits.collectAsState()
|
||||
|
||||
val isUpToDate by viewModel.isUpToDate.collectAsState()
|
||||
|
||||
val distance = userLocation?.distanceTo(location.toAndroidLocation())
|
||||
|
||||
Row(modifier = modifier) {
|
||||
SharedTransitionLayout(
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedContent(showDetails) { showDetails ->
|
||||
if (!showDetails) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
modifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = slideIn { IntOffset(-it.width, 0) } + fadeIn(),
|
||||
exit = slideOut { IntOffset(-it.width, 0) } + fadeOut(),
|
||||
)
|
||||
.padding(8.dp),
|
||||
size = 48.dp,
|
||||
icon = { icon },
|
||||
badge = { badge },
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = location.labelOverride ?: location.label,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier
|
||||
.sharedBounds(
|
||||
rememberSharedContentState("label"),
|
||||
this@AnimatedContent
|
||||
)
|
||||
)
|
||||
val category = location.category
|
||||
val formattedDistance = distance?.metersToLocalizedString(
|
||||
context, imperialUnits
|
||||
)
|
||||
if (category != null || formattedDistance != null) {
|
||||
Text(
|
||||
when {
|
||||
category != null && formattedDistance != null -> "${category} • ${formattedDistance}"
|
||||
category != null -> category.toString()
|
||||
formattedDistance != null -> formattedDistance
|
||||
else -> ""
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.sharedElement(
|
||||
rememberSharedContentState("sublabel"),
|
||||
this@AnimatedContent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = slideIn { IntOffset(it.width, 0) } + fadeIn(),
|
||||
exit = slideOut { IntOffset(it.width, 0) } + fadeOut(),
|
||||
)
|
||||
.padding(end = 8.dp)
|
||||
.size(48.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
MaterialTheme.shapes.small
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
if (targetHeading != null) Icons.Rounded.Navigation else Icons.Rounded.FiberManualRecord,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.rotate(targetHeading ?: 0f),
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val showMap by viewModel.showMap.collectAsState()
|
||||
Column {
|
||||
if (showMap) {
|
||||
val tileServerUrl by viewModel.mapTileServerUrl.collectAsState()
|
||||
val shape = MaterialTheme.shapes.small
|
||||
|
||||
val applyTheming by viewModel.applyMapTheming.collectAsState()
|
||||
val showPositionOnMap by viewModel.showPositionOnMap.collectAsState()
|
||||
MapTiles(
|
||||
modifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = expandVertically(),
|
||||
exit = shrinkOut(),
|
||||
)
|
||||
.padding(12.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.fillMaxWidth()
|
||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.clickable {
|
||||
viewModel.launch(context)
|
||||
},
|
||||
tileServerUrl = tileServerUrl,
|
||||
location = location,
|
||||
maxZoomLevel = 19,
|
||||
tiles = IntSize(3, 2),
|
||||
applyTheming = applyTheming,
|
||||
userLocation = {
|
||||
if (showPositionOnMap) userLocation?.let {
|
||||
UserLocation(
|
||||
it.latitude,
|
||||
it.longitude,
|
||||
heading = userHeading,
|
||||
)
|
||||
} else null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = location.labelOverride ?: location.label,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.sharedBounds(
|
||||
rememberSharedContentState("label"),
|
||||
this@AnimatedContent
|
||||
)
|
||||
)
|
||||
val category = location.category
|
||||
val formattedDistance = distance?.metersToLocalizedString(
|
||||
context, imperialUnits
|
||||
)
|
||||
if (category != null || formattedDistance != null) {
|
||||
Text(
|
||||
when {
|
||||
category != null && formattedDistance != null -> "${category} • ${formattedDistance}"
|
||||
category != null -> category.toString()
|
||||
formattedDistance != null -> formattedDistance
|
||||
else -> ""
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.sharedElement(
|
||||
rememberSharedContentState("sublabel"),
|
||||
this@AnimatedContent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val openingSchedule = location.openingSchedule
|
||||
if (openingSchedule != null && (openingSchedule.isTwentyFourSeven || openingSchedule.openingHours.isNotEmpty())) {
|
||||
var showOpeningSchedule by remember(openingSchedule) {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 12.dp, top = 12.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
onClick = {
|
||||
if (!openingSchedule.isTwentyFourSeven) {
|
||||
showOpeningSchedule = true
|
||||
}
|
||||
}
|
||||
) {
|
||||
AnimatedContent(showOpeningSchedule) { showSchedule ->
|
||||
if (!showSchedule) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (openingSchedule.isTwentyFourSeven) {
|
||||
Text(
|
||||
text = stringResource(R.string.location_open_24_7),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
} else {
|
||||
val text = remember(openingSchedule) {
|
||||
val currentOpeningTime =
|
||||
openingSchedule.getCurrentOpeningHours()
|
||||
val timeFormat =
|
||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
return@remember if (currentOpeningTime != null) {
|
||||
val isSameDay =
|
||||
currentOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime =
|
||||
timeFormat.format(currentOpeningTime.startTime + currentOpeningTime.duration)
|
||||
val closingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_closes,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow = currentOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_closes_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_open)} • $closingTime"
|
||||
} else {
|
||||
val nextOpeningTime =
|
||||
openingSchedule.getNextOpeningHours()
|
||||
val isSameDay = nextOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime = timeFormat.format(nextOpeningTime.startTime)
|
||||
val openingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_opens,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow = nextOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_opens_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_closed)} • $openingTime"
|
||||
}
|
||||
}
|
||||
|
||||
Text(text = text, style = MaterialTheme.typography.labelMedium, modifier = Modifier.weight(1f))
|
||||
|
||||
Icon(Icons.AutoMirrored.Rounded.NavigateNext, null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 6.dp)
|
||||
) {
|
||||
val groups = remember(openingSchedule) {
|
||||
openingSchedule.openingHours
|
||||
.groupBy { it.dayOfWeek }.entries
|
||||
.sortedBy { it.key }
|
||||
}
|
||||
|
||||
for (group in groups) {
|
||||
Row(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 2.dp,
|
||||
horizontal = 12.dp
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = group.key.getDisplayName(
|
||||
TextStyle.FULL,
|
||||
Locale.getDefault()
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
val times = remember(group.value) {
|
||||
val dateFormatter =
|
||||
DateTimeFormatter.ofLocalizedTime(
|
||||
FormatStyle.SHORT
|
||||
)
|
||||
group.value.sortedBy { it.startTime }
|
||||
.joinToString(separator = ", ") {
|
||||
"${it.startTime.format(dateFormatter)}–${
|
||||
it.startTime.plus(
|
||||
it.duration
|
||||
).format(dateFormatter)
|
||||
}"
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = times,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(start = 12.dp, top = 8.dp)
|
||||
) {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
onClick = {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("google.navigation:q=${location.latitude},${location.longitude}")
|
||||
),
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.menu_navigation)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Navigation, null,
|
||||
modifier = Modifier.size(AssistChipDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
)
|
||||
location.phoneNumber?.let {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
onClick = {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_DIAL, Uri.parse("tel:$it")
|
||||
)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.menu_dial)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Phone, null,
|
||||
modifier = Modifier.size(AssistChipDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
location.websiteUrl?.let {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
onClick = {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(it)
|
||||
)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.menu_website)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Language, null,
|
||||
modifier = Modifier.size(AssistChipDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val toolbarActions = mutableListOf<ToolbarAction>()
|
||||
|
||||
if (LocalFavoritesEnabled.current) {
|
||||
val isPinned by viewModel.isPinned.collectAsState(false)
|
||||
val favAction = if (isPinned) {
|
||||
DefaultToolbarAction(
|
||||
label = stringResource(R.string.menu_favorites_unpin),
|
||||
icon = Icons.Rounded.Star,
|
||||
action = {
|
||||
viewModel.unpin()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
DefaultToolbarAction(
|
||||
label = stringResource(R.string.menu_favorites_pin),
|
||||
icon = Icons.Rounded.StarOutline,
|
||||
action = {
|
||||
viewModel.pin()
|
||||
})
|
||||
}
|
||||
toolbarActions.add(favAction)
|
||||
}
|
||||
|
||||
if (!showMap) {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_map),
|
||||
icon = Icons.Rounded.Map
|
||||
) {
|
||||
viewModel.launch(context)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
location.fixMeUrl?.let {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_bugreport),
|
||||
icon = Icons.Rounded.BugReport,
|
||||
) {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(location.fixMeUrl)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Toolbar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leftActions = listOf(DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_back),
|
||||
icon = Icons.AutoMirrored.Rounded.ArrowBack
|
||||
) {
|
||||
onBack()
|
||||
}),
|
||||
rightActions = toolbarActions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Row(modifier = modifier) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -136,8 +565,6 @@ fun LocationItem(
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
||||
val badge by viewModel.badge.collectAsState(null)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(52.dp)
|
||||
@ -283,56 +710,20 @@ fun LocationItem(
|
||||
}
|
||||
}
|
||||
|
||||
val showMap by viewModel.showMap.collectAsState()
|
||||
if (showMap) {
|
||||
val zoomLevel = 19
|
||||
val nTiles = 4
|
||||
|
||||
val tileServerUrl by viewModel.mapTileServerUrl.collectAsState()
|
||||
val shape = MaterialTheme.shapes.small
|
||||
HorizontalDivider()
|
||||
|
||||
val applyTheming by viewModel.applyMapTheming.collectAsState()
|
||||
val showPositionOnMap by viewModel.showPositionOnMap.collectAsState()
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
MapTiles(
|
||||
val address = buildAddress(location.street, location.houseNumber)
|
||||
if (address != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp, bottom = 8.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.fillMaxWidth(.9125f)
|
||||
.border(1.dp, MaterialTheme.colorScheme.outline, shape)
|
||||
.clip(shape)
|
||||
.clickable {
|
||||
viewModel.launch(context)
|
||||
},
|
||||
tileServerUrl = tileServerUrl,
|
||||
location = location,
|
||||
maxZoomLevel = zoomLevel,
|
||||
tiles = IntSize(3, 2),
|
||||
applyTheming = applyTheming,
|
||||
userLocation = {
|
||||
if (showPositionOnMap) userLocation?.let {
|
||||
UserLocation(
|
||||
it.latitude,
|
||||
it.longitude,
|
||||
heading = userHeading,
|
||||
)
|
||||
} else null
|
||||
},
|
||||
.padding(bottom = 8.dp),
|
||||
text = address,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
|
||||
val address = buildAddress(location.street, location.houseNumber)
|
||||
if (address != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 8.dp),
|
||||
text = address,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(Modifier.padding(top = 8.dp))
|
||||
@ -378,97 +769,10 @@ fun LocationItem(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
|
||||
val toolbarActions = mutableListOf<ToolbarAction>()
|
||||
|
||||
if (LocalFavoritesEnabled.current) {
|
||||
val isPinned by viewModel.isPinned.collectAsState(false)
|
||||
val favAction = if (isPinned) {
|
||||
DefaultToolbarAction(
|
||||
label = stringResource(R.string.menu_favorites_unpin),
|
||||
icon = Icons.Rounded.Star,
|
||||
action = {
|
||||
viewModel.unpin()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
DefaultToolbarAction(
|
||||
label = stringResource(R.string.menu_favorites_pin),
|
||||
icon = Icons.Rounded.StarOutline,
|
||||
action = {
|
||||
viewModel.pin()
|
||||
})
|
||||
}
|
||||
toolbarActions.add(favAction)
|
||||
}
|
||||
|
||||
if (!showMap) {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_map),
|
||||
icon = Icons.Rounded.Map
|
||||
) {
|
||||
viewModel.launch(context)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
location.phoneNumber?.let {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_dial),
|
||||
icon = Icons.Rounded.Phone
|
||||
) {
|
||||
viewModel.viewModelScope.launch {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_DIAL, Uri.parse("tel:$it")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
location.websiteUrl?.let {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_website),
|
||||
icon = Icons.Rounded.TravelExplore
|
||||
) {
|
||||
viewModel.viewModelScope.launch {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(it)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
location.fixMeUrl?.let {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_bugreport),
|
||||
icon = Icons.Rounded.BugReport,
|
||||
) {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(location.fixMeUrl)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Toolbar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leftActions = listOf(DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_back),
|
||||
icon = Icons.AutoMirrored.Rounded.ArrowBack
|
||||
) {
|
||||
onBack()
|
||||
}),
|
||||
rightActions = toolbarActions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -521,3 +825,25 @@ private fun buildAddress(
|
||||
}
|
||||
return if (summary.isEmpty()) null else summary.toString()
|
||||
}
|
||||
|
||||
private fun OpeningSchedule.getCurrentOpeningHours(): OpeningHours? {
|
||||
return openingHours.find { it.isOpen }
|
||||
}
|
||||
|
||||
private fun OpeningSchedule.getNextOpeningHours(): OpeningHours {
|
||||
val now = LocalDateTime.now()
|
||||
val sortedSchedule = this
|
||||
.openingHours
|
||||
.sortedWith { a, b ->
|
||||
if (a.dayOfWeek == b.dayOfWeek) {
|
||||
a.startTime.compareTo(b.startTime)
|
||||
} else {
|
||||
a.dayOfWeek.compareTo(b.dayOfWeek)
|
||||
}
|
||||
}
|
||||
|
||||
return sortedSchedule
|
||||
.find {
|
||||
now.dayOfWeek < it.dayOfWeek || now.dayOfWeek == it.dayOfWeek && now.toLocalTime() < it.startTime
|
||||
} ?: sortedSchedule.first()
|
||||
}
|
||||
@ -23,9 +23,11 @@ import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Navigation
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
@ -185,31 +187,6 @@ fun MapTiles(
|
||||
),
|
||||
label = "poiLocAnimation"
|
||||
)
|
||||
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val osmAttributionTextStyle = MaterialTheme.typography.labelSmall
|
||||
val osmAttributionTextColor = MaterialTheme.colorScheme.onSurface
|
||||
val osmAttributionSurface =
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = .5f)
|
||||
|
||||
val (yLocation, xLocation) = remember(location, zoom) {
|
||||
getTileCoordinates(
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
zoom
|
||||
)
|
||||
}
|
||||
val userOffset = remember(userLocation, zoom) {
|
||||
if (userLocation != null) {
|
||||
getTileCoordinates(
|
||||
latitude = userLocation.lat,
|
||||
longitude = userLocation.lon,
|
||||
zoom
|
||||
)
|
||||
} else {
|
||||
Offset.Unspecified
|
||||
}
|
||||
}
|
||||
val tileSize = minWidth / tiles.width.toFloat()
|
||||
val locationTileCoordinates =
|
||||
getTileCoordinates(location.latitude, location.longitude, zoom)
|
||||
@ -275,6 +252,18 @@ fun MapTiles(
|
||||
.border(2.dp, MaterialTheme.colorScheme.onTertiary, CircleShape)
|
||||
.shadow(1.dp, CircleShape)
|
||||
)
|
||||
|
||||
if (osmAttribution != null) {
|
||||
Text(
|
||||
text = osmAttribution,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = .5f))
|
||||
.padding(top = 2.dp, bottom = 2.dp, start = 4.dp, end = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -666,7 +666,6 @@
|
||||
<string name="missing_permission_plugins">Es requereix permís de connector per utilitzar connectors.</string>
|
||||
<string name="preference_plugin_badges_summary">Indiqueu amb quin connector s\'ha creat un resultat de cerca</string>
|
||||
<string name="preference_mdy_color_source_wallpaper">Colors del Fons de Pantalla</string>
|
||||
<string name="location_open_next_day">Estarà disponible %1$s</string>
|
||||
<string name="missing_permission_location_search">Doneu permís a la ubicació per cercar ubicacions properes.</string>
|
||||
<string name="preference_mdy_color_source">Font de Colors dinàmics</string>
|
||||
<string name="preference_mdy_color_source_system">Colors del Sistema</string>
|
||||
|
||||
@ -688,7 +688,6 @@
|
||||
<string name="preference_search_locations_radius">Vzdálenost vyhledávání</string>
|
||||
<string name="preference_search_locations_summary">Vyhledat v místní oblasti obchody a další místa</string>
|
||||
<string name="preference_search_locations_show_position_on_map_summary">Zobrazit vlastní lokaci na mapě</string>
|
||||
<string name="location_open_next_day">Otevírá v %1$s</string>
|
||||
<string name="preference_search_locations_show_map_summary">Zobrazit náhled mapy pro místa</string>
|
||||
<string name="preference_search_locations_theme_map">Téma mapy</string>
|
||||
<string name="preference_search_locations_theme_map_summary">Použít barevné schéma launcheru jako téma mapy</string>
|
||||
|
||||
@ -678,7 +678,6 @@
|
||||
<string name="default_websearch_3_url">https://play.google.com/store/search?q=${1}</string>
|
||||
<string name="shortcut_unavailable_description">Denne genvej er utilgængelig, fordi %1$s ikke er din standard launcher</string>
|
||||
<string name="default_websearch_2_url">https://www.youtube.com/results?search_query=${1}</string>
|
||||
<string name="location_open_next_day">Åbner på %1$s</string>
|
||||
<string name="location_open">Åben</string>
|
||||
<string name="location_closed">Lukket</string>
|
||||
<string name="location_open_until">Åben indtil %1$s</string>
|
||||
|
||||
@ -681,7 +681,6 @@
|
||||
<string name="preference_mdy_color_source">Quelle für dynamische Farben</string>
|
||||
<string name="preference_search_locations_show_position_on_map_summary">Den eigenen Standort auf der Karte zeigen</string>
|
||||
<string name="location_closed">Geschlossen</string>
|
||||
<string name="location_open_next_day">Öffnet am %1$s</string>
|
||||
<string name="preference_search_location_custom_overpass_url">Overpass-URL</string>
|
||||
<string name="preference_search_locations">Orte</string>
|
||||
<string name="location_open">Geöffnet</string>
|
||||
|
||||
@ -703,7 +703,6 @@
|
||||
<string name="clock_style_analog">Manecillas</string>
|
||||
<string name="clock_style_empty">Sin reloj</string>
|
||||
<string name="clock_variant_standard">Estándar</string>
|
||||
<string name="location_open_next_day">Abre el %1$s</string>
|
||||
<string name="preference_search_locations_show_map">Mapa</string>
|
||||
<string name="preference_search_locations_show_map_summary">Mostrar una vista previa de mapa para los lugares</string>
|
||||
<string name="preference_search_locations_theme_map">Tema del mapa</string>
|
||||
|
||||
@ -688,7 +688,6 @@
|
||||
<string name="preference_search_locations_hide_uncategorized">Kategorizálatlan helyek elrejtése</string>
|
||||
<string name="preference_search_locations_hide_uncategorized_summary">Csak jól meghatározott kategóriák, például kávézók, vagy éttermek eredményeit jeleníti meg</string>
|
||||
<string name="preference_search_locations_show_map">Térkép</string>
|
||||
<string name="location_open_next_day">Kinyit ekkor: %1$s</string>
|
||||
<string name="preference_search_locations_theme_map">Térkép színséma</string>
|
||||
<string name="preference_search_locations_theme_map_summary">Az indító színsémájának alkalmazása a térképre</string>
|
||||
<string name="preference_search_locations_show_position_on_map">Saját helyszín megjelenítése</string>
|
||||
|
||||
@ -687,7 +687,6 @@
|
||||
<string name="preference_search_location_custom_overpass_url">URL Overpass</string>
|
||||
<string name="preference_search_bar_separate_work_profile">App per profili di lavoro separate</string>
|
||||
<string name="preference_search_bar_separate_work_profile_summary">Mostra app del profilo di lavoro in una scheda separata</string>
|
||||
<string name="location_open_next_day">Apertura %1$s</string>
|
||||
<string name="preference_search_locations_hide_uncategorized">Nascondi luoghi senza categoria</string>
|
||||
<string name="preference_clock_widget_show_seconds">Mostra secondi</string>
|
||||
<string name="widget_use_theme_colors">Usa colore del tema</string>
|
||||
|
||||
@ -347,7 +347,6 @@
|
||||
<string name="location_closed">Fechado</string>
|
||||
<string name="location_open_until">Aberto até %1$s</string>
|
||||
<string name="location_open_next">Abre em %1$s</string>
|
||||
<string name="location_open_next_day">Abre no/na %1$s</string>
|
||||
<string name="menu_map">Ver no mapa</string>
|
||||
<string name="menu_website">Abrir página web</string>
|
||||
</resources>
|
||||
@ -691,7 +691,6 @@
|
||||
<string name="missing_permission_location_search">Для поиска мест поблизости требуется разрешение на доступ к местоположению.</string>
|
||||
<string name="location_open_until">Закроется в %1$s</string>
|
||||
<string name="location_open_next">Откроется в %1$s</string>
|
||||
<string name="location_open_next_day">Откроется в %1$s</string>
|
||||
<string name="menu_map">Показать на карте</string>
|
||||
<string name="preference_search_locations">Места</string>
|
||||
<string name="preference_search_locations_radius">Радиус поиска</string>
|
||||
|
||||
@ -195,7 +195,6 @@
|
||||
<string name="location_closed">Kapalı</string>
|
||||
<string name="location_open_until">Saat %1$s\'e kadar açık</string>
|
||||
<string name="location_open_next">%1$s Sonra açılacaktır</string>
|
||||
<string name="location_open_next_day">%1$s Günü açılacaktır</string>
|
||||
<string name="menu_map">Haritada göster</string>
|
||||
<string name="menu_website">Websiteyi aç</string>
|
||||
<string name="calendar_widget_pinned_events">Yakında</string>
|
||||
|
||||
@ -2,12 +2,14 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="en">
|
||||
<string name="location_open">Open</string>
|
||||
<string name="location_closed">Closed</string>
|
||||
<!-- Shown when a location search result is open until TIMESTAMP (hours:minutes) of the same day -->
|
||||
<string name="location_open_until">Open until %1$s</string>
|
||||
<!-- Shown when a location search result opens in TIMESPAN on the same day -->
|
||||
<string name="location_open_next">Opens in %1$s</string>
|
||||
<!-- Shown when a location search result opens next DAY-OF-WEEK -->
|
||||
<string name="location_open_next_day">Opens on %1$s</string>
|
||||
<!-- Time when a place next closes (on the same day). $1%s: time (hh:mm) -->
|
||||
<string name="location_closes">closes at %1$s</string>
|
||||
<!-- Time when a place next closes (on a different day). $1%s: day of week, %2$s: time (hh:mm) -->
|
||||
<string name="location_closes_other_day">closes %1$s, %2$s</string>
|
||||
<!-- Time when a place next opens (on the same day). $1%s: time (hh:mm) -->
|
||||
<string name="location_opens">opens at %1$s</string>
|
||||
<!-- Time when a place next opens (on a different day). $1%s: day of week, %2$s: time (hh:mm) -->
|
||||
<string name="location_opens_other_day">opens %1$s, %2$s</string>
|
||||
<!-- Name of the app -->
|
||||
<!-- Share something. Can be anything, an app, a website, a file, … -->
|
||||
<string name="menu_share">Share</string>
|
||||
@ -893,6 +895,7 @@
|
||||
<string name="preference_search_locations_show_position_on_map">Show own position</string>
|
||||
<string name="preference_search_location_custom_tile_server_url">Tileserver URL</string>
|
||||
<string name="menu_dial">Call</string>
|
||||
<string name="menu_navigation">Navigation</string>
|
||||
<string name="menu_bugreport">Bug Report</string>
|
||||
<string name="location_open_24_7">Open 24/7</string>
|
||||
<string name="plugin_state_error">This plugin isn\'t working correctly</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user