diff --git a/CHANGELOG.md b/CHANGELOG.md index c08fa65..e8fe971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.22.1] — 2026-10-02 + +### Fixed +- **Links in an event's location open in the browser.** Tapping a location that holds a URL, such as a video-call link, opened it in a maps app; maps is now only used for plain addresses ([#357]). + ## [2.22.0] — 2026-10-01 ### Added @@ -1817,3 +1822,4 @@ automatically, with zero telemetry and no internet permission. [#322]: https://codeberg.org/jlmakiola/calendula/issues/322 [#331]: https://codeberg.org/jlmakiola/calendula/issues/331 [#336]: https://codeberg.org/jlmakiola/calendula/issues/336 +[#357]: https://codeberg.org/jlmakiola/calendula/issues/357 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 17ed862..306f2d8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,8 +28,8 @@ android { // which builds this version and then creates the matching vX.Y.Z tag + // release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 + // PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md. - versionCode = 22200 - versionName = "2.22.0" + versionCode = 22201 + versionName = "2.22.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt index db61df9..0b79bfa 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt @@ -534,7 +534,7 @@ private fun EventDetailContent( ) } - // Location (conditional, tap → maps). + // Location (conditional, tap → link if it holds a URL, else maps). instance.location?.takeIf { it.isNotBlank() }?.let { location -> val context = LocalContext.current val locationLabel = stringResource(R.string.event_detail_location) @@ -553,7 +553,7 @@ private fun EventDetailContent( label = locationLabel, text = location, copy = copyField, - onTap = { openInMaps(context, location) }, + onTap = { openLocation(context, location) }, ) .padding(vertical = 2.dp), ) @@ -846,19 +846,22 @@ private fun foreignTimeZone( ) } +private val urlRegex = Regex("""https?://\S+""") + +/** Strip trailing punctuation that commonly abuts a URL in prose. */ +private fun String.trimUrlPunctuation(): String = + trimEnd('.', ',', ';', ':', '!', '?', ')', ']', '"', '\'') + /** Wrap http(s) URLs in [text] as tappable links tinted [linkColor]. */ @Composable private fun linkifyUrls(text: String, linkColor: Color): AnnotatedString = remember(text, linkColor) { - val regex = Regex("""https?://\S+""") val styles = TextLinkStyles( style = SpanStyle(color = linkColor, textDecoration = TextDecoration.Underline), ) buildAnnotatedString { append(text) - for (match in regex.findAll(text)) { - // Trim trailing punctuation that commonly abuts a URL in prose. - val raw = match.value - val url = raw.trimEnd('.', ',', ';', ':', '!', '?', ')', ']', '"', '\'') + for (match in urlRegex.findAll(text)) { + val url = match.value.trimUrlPunctuation() val end = match.range.first + url.length addLink(LinkAnnotation.Url(url, styles), match.range.first, end) } @@ -915,6 +918,20 @@ private fun formatWhen( private fun Instant.toJavaLocalDateTime(zid: ZoneId): java.time.LocalDateTime = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(toEpochMilliseconds()), zid) +/** Open the first URL in [location] (e.g. a meeting link), otherwise search it in maps. */ +private fun openLocation(context: Context, location: String) { + val url = urlRegex.find(location)?.value?.trimUrlPunctuation() + if (url == null) { + openInMaps(context, location) + return + } + try { + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + } catch (e: ActivityNotFoundException) { + // No app handles the link — nothing sensible to do; swallow. + } +} + /** Open a maps intent for [query]; fall back to a web maps URL if no app handles geo:. */ private fun openInMaps(context: Context, query: String) { val encoded = Uri.encode(query) diff --git a/fastlane/metadata/android/en-US/changelogs/22201.txt b/fastlane/metadata/android/en-US/changelogs/22201.txt new file mode 100644 index 0000000..cf12b0b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/22201.txt @@ -0,0 +1,2 @@ +Fixed +• Links in an event's location open in the browser. Tapping a video-call link in the location opened a maps app instead.