Add map coordinates to clipboard feature.
This commit is contained in:
parent
9dbfb9c3a0
commit
62b41e664d
10 changed files with 119 additions and 48 deletions
|
|
@ -22,7 +22,7 @@ class MarqueeParser @Inject constructor() {
|
|||
|
||||
item is List<*> -> {
|
||||
val map = item.getOrNull(structure.map) as? String
|
||||
val name = item.getOrNull(structure.name) as? String
|
||||
val name = item.getOrNull(structure.name) as? String?
|
||||
val x = (item.getOrNull(structure.x) as? String)
|
||||
?.replace(oldValue = ",", newValue = ".")
|
||||
?.toFloatOrNull()
|
||||
|
|
@ -31,15 +31,15 @@ class MarqueeParser @Inject constructor() {
|
|||
?.toFloatOrNull()
|
||||
val description = item.getOrNull(structure.description) as? String?
|
||||
|
||||
if (map != null && name != null) {
|
||||
if (map != null) {
|
||||
Location.Marquee(
|
||||
map = map,
|
||||
name = name,
|
||||
name = name?.takeIf { it.isNotBlank() },
|
||||
position = when {
|
||||
x != null && y != null -> Offset(x, y)
|
||||
else -> Offset.Unspecified
|
||||
},
|
||||
description = description,
|
||||
description = description?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ data class Location(
|
|||
@Stable
|
||||
data class Marquee(
|
||||
val map: String,
|
||||
val name: String,
|
||||
val name: String?,
|
||||
val position: Offset,
|
||||
val description: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,11 +56,14 @@ class LocationRepository @Inject constructor(
|
|||
|
||||
companion object {
|
||||
private const val TAG = "LocationRepository"
|
||||
private const val ROOT = "https://docs.google.com/spreadsheets"
|
||||
const val SHEET_URL = "$ROOT/d/${Sheet.ID}/edit#gid=${Sheet.MARQUEE_GID}"
|
||||
}
|
||||
|
||||
private object Sheet {
|
||||
const val ID = "1gbWaqXChz8pDJ-O3-Q8a_0wbDAYGlVA2voL7nEJUSn0"
|
||||
const val MAP = "carte"
|
||||
const val MARQUEE = "marqueur"
|
||||
const val MARQUEE_GID = "1581979177"
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import androidx.compose.material3.IconButtonDefaults
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
|
|
@ -53,7 +54,9 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
|
|
@ -94,8 +97,10 @@ data class AnnotatedLocationDetailUio(
|
|||
fun LocationDetail(
|
||||
viewModel: LocationDetailViewModel = hiltViewModel()
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val screen = LocalScreenNavHost.current
|
||||
val snack = LocalSnack.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val scroll = rememberScrollState()
|
||||
|
|
@ -136,11 +141,20 @@ fun LocationDetail(
|
|||
onMapTap = {
|
||||
snackJob.value?.cancel()
|
||||
snackJob.value = scope.launch {
|
||||
snack.showSnackbar(
|
||||
val result = snack.showSnackbar(
|
||||
message = "x:${it.x}, y:${it.y}",
|
||||
actionLabel = ok,
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
viewModel.clip(
|
||||
label = context.getString(R.string.map_label),
|
||||
coordinate = it,
|
||||
)
|
||||
uriHandler.openUri(
|
||||
uri = viewModel.sheetUri,
|
||||
)
|
||||
}
|
||||
mapHighlight.value = Offset.Unspecified
|
||||
}
|
||||
mapHighlight.value = it
|
||||
|
|
@ -162,7 +176,7 @@ fun LocationDetail(
|
|||
if (newScale <= fantasy.maxScale) {
|
||||
val oldScale = fantasy.scale
|
||||
val oldOffset = fantasy.offset
|
||||
fantasy.scale(newScale)
|
||||
fantasy.scale(scale = newScale)
|
||||
fantasy.pan(
|
||||
offset = Offset(
|
||||
x = oldOffset.x * newScale / oldScale,
|
||||
|
|
@ -176,7 +190,7 @@ fun LocationDetail(
|
|||
if (newScale >= fantasy.minScale) {
|
||||
val oldScale = fantasy.scale
|
||||
val oldOffset = fantasy.offset
|
||||
fantasy.scale(newScale)
|
||||
fantasy.scale(scale = newScale)
|
||||
fantasy.pan(
|
||||
offset = Offset(
|
||||
x = oldOffset.x * newScale / oldScale,
|
||||
|
|
@ -259,12 +273,6 @@ private fun LocationContent(
|
|||
modifier = Modifier.verticalScroll(state = scrollState),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(
|
||||
min = this@constraint.maxHeight / 2,
|
||||
max = this@constraint.maxHeight * 2 / 3,
|
||||
),
|
||||
tonalElevation = 2.dp,
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -272,8 +280,12 @@ private fun LocationContent(
|
|||
) {
|
||||
FantasyMap(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.Center)
|
||||
.offset { IntOffset(x = 0, y = scrollState.value / 2) },
|
||||
.fillMaxWidth()
|
||||
.heightIn(
|
||||
min = this@constraint.maxHeight / 2,
|
||||
max = this@constraint.maxHeight * 2 / 3,
|
||||
)
|
||||
.offset(scrollState = scrollState),
|
||||
state = fantasyMapState,
|
||||
model = { item.value.map },
|
||||
previewPlaceholder = R.drawable.im_brulkhai,
|
||||
|
|
@ -483,6 +495,11 @@ private fun rememberScrollConnection(
|
|||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
private fun Modifier.offset(scrollState: ScrollState) = this.offset {
|
||||
IntOffset(x = 0, y = scrollState.value / 2)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,31 @@
|
|||
package com.pixelized.rplexicon.ui.screens.location.detail
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.rplexicon.repository.LocationRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.locationDetailArgument
|
||||
import com.pixelized.rplexicon.utilitary.cells
|
||||
import com.pixelized.rplexicon.utilitary.line
|
||||
import com.pixelized.rplexicon.utilitary.table
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LocationDetailViewModel @Inject constructor(
|
||||
application: Application,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
repository: LocationRepository,
|
||||
) : ViewModel() {
|
||||
) : AndroidViewModel(application) {
|
||||
private val clipboard = application.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val sheetUri = LocationRepository.SHEET_URL
|
||||
val location: State<LocationDetailUio>
|
||||
|
||||
init {
|
||||
|
|
@ -34,4 +46,23 @@ class LocationDetailViewModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun clip(
|
||||
label: String,
|
||||
coordinate: Offset,
|
||||
) {
|
||||
val clip = ClipData.newHtmlText(
|
||||
label,
|
||||
"${coordinate.x}, ${coordinate.y}",
|
||||
table {
|
||||
line {
|
||||
cells(
|
||||
coordinate.x.toString().replace(".", ","),
|
||||
coordinate.y.toString().replace(".", ","),
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,14 +26,14 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
|||
|
||||
@Stable
|
||||
data class MarqueeUio(
|
||||
val name: String,
|
||||
val name: String?,
|
||||
val position: Offset,
|
||||
val description: String?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class AnnotatedMarqueeUio(
|
||||
val name: AnnotatedString,
|
||||
val name: AnnotatedString?,
|
||||
val position: Offset,
|
||||
val description: AnnotatedString?,
|
||||
)
|
||||
|
|
@ -43,9 +43,13 @@ fun MarqueeUio.annotate(
|
|||
typography: LexiconTypography
|
||||
): AnnotatedMarqueeUio {
|
||||
return AnnotatedMarqueeUio(
|
||||
name = name.annotateWithDropCap(style = typography.bodyDropCapSpan),
|
||||
name = name?.annotateWithDropCap(
|
||||
style = typography.bodyDropCapSpan,
|
||||
),
|
||||
position = position,
|
||||
description = description?.annotateWithDropCap(style = typography.bodyDropCapSpan),
|
||||
description = description?.annotateWithDropCap(
|
||||
style = typography.bodyDropCapSpan,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -58,21 +62,23 @@ fun MarqueeItem(
|
|||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = LOS_HOLLOW,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 3,
|
||||
text = marquee.name,
|
||||
)
|
||||
marquee.name?.let {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = LOS_HOLLOW,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 3,
|
||||
text = marquee.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
marquee.description?.let {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.pixelized.rplexicon.utilitary
|
||||
|
||||
inline fun table(crossinline block: () -> String): String = "<table>${block()}</table>"
|
||||
|
||||
inline fun line(crossinline block: () -> String): String = "<tr>${block()}</tr>"
|
||||
|
||||
inline fun cell(crossinline block: () -> String): String = "<td>${block()}</td>"
|
||||
|
||||
fun cells(vararg values: String): String = values.joinToString(separator = "") { cell { it } }
|
||||
|
|
@ -1,19 +1,27 @@
|
|||
package com.pixelized.rplexicon.utilitary.extentions
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private val dropCapRegex = Regex(
|
||||
pattern = "(?:^|\n\n)([A-Z])"
|
||||
)
|
||||
|
||||
val String.ARG: String get() = "$this={$this}"
|
||||
|
||||
@Stable
|
||||
val String?.highlightRegex: Regex?
|
||||
get() = this?.takeIf { it.isNotEmpty() }?.let {
|
||||
Regex(pattern = Regex.escape(it), option = RegexOption.IGNORE_CASE)
|
||||
Regex(
|
||||
pattern = Regex.escape(it),
|
||||
option = RegexOption.IGNORE_CASE,
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
val String?.finderRegex: Regex?
|
||||
get() = this?.takeIf { it.isNotEmpty() }?.let {
|
||||
Regex(
|
||||
|
|
@ -28,18 +36,13 @@ fun String?.toUriOrNull(): Uri? = try {
|
|||
null
|
||||
}
|
||||
|
||||
private val dropCapRegex = Regex("(?:\n\n)(.)")
|
||||
|
||||
@Stable
|
||||
fun String.annotateWithDropCap(
|
||||
style: SpanStyle,
|
||||
) = AnnotatedString(
|
||||
text = this,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = style,
|
||||
start = 0,
|
||||
end = Integer.min(1, this.length),
|
||||
),
|
||||
) + dropCapRegex.annotatedSpan(input = this, spanStyle = style)
|
||||
spanStyles = dropCapRegex.annotatedSpan(
|
||||
input = this,
|
||||
spanStyle = style,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,4 +54,5 @@
|
|||
<string name="quest_detail_group_rewars">Récompense de groupe :</string>
|
||||
|
||||
<string name="map_title">Carte</string>
|
||||
<string name="map_label">Coordonnées</string>
|
||||
</resources>
|
||||
|
|
@ -54,4 +54,5 @@
|
|||
<string name="quest_detail_group_rewars">Group reward:</string>
|
||||
|
||||
<string name="map_title">Map</string>
|
||||
<string name="map_label">Coordinates</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue