Add map coordinates to clipboard feature.

This commit is contained in:
Thomas Andres Gomez 2023-08-17 15:33:09 +02:00
parent 9dbfb9c3a0
commit 62b41e664d
10 changed files with 119 additions and 48 deletions

View file

@ -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

View file

@ -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?,
)

View file

@ -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"
}
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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(

View file

@ -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 } }

View file

@ -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,
)
)

View file

@ -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>

View file

@ -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>