feat: add robots.txt and sitemap.xml routes for SEO optimization
- Implemented a new route for robots.txt to control crawler access. - Added a sitemap.xml route to provide search engines with a list of site URLs. - Included functions for URL normalization and XML escaping to ensure proper formatting.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package de.harheimertc.ui.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -72,32 +74,62 @@ private fun CompactNavigation(
|
||||
onNavigate: (String) -> Unit,
|
||||
navigationState: NavigationUiState = NavigationUiState(),
|
||||
) {
|
||||
val section = menuSection(selectedRoute)
|
||||
val routeSection = menuSection(selectedRoute)
|
||||
val sectionOverride = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf<MenuSection?>(null) }
|
||||
val section = routeSection ?: sectionOverride.value
|
||||
val subItems = submenu(section, navigationState)
|
||||
var cmsExpanded = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(false) }
|
||||
val navigateAndClose: (String) -> Unit = { route -> cmsExpanded.value = false; onNavigate(route) }
|
||||
val mainScroll = rememberScrollState()
|
||||
val subScroll = rememberScrollState()
|
||||
val cmsSubScroll = rememberScrollState()
|
||||
|
||||
BrandRow(onLogin = { onNavigate(Destinations.Login.route) })
|
||||
Row(
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
CompactLink("Start", Destinations.Home.route, selectedRoute, onNavigate)
|
||||
CompactLink("Verein", Destinations.VereinAbout.route, selectedRoute, onNavigate)
|
||||
CompactLink("Mannschaften", Destinations.Mannschaften.route, selectedRoute, onNavigate)
|
||||
CompactLink("Training", Destinations.Training.route, selectedRoute, onNavigate)
|
||||
CompactLink("Termine", Destinations.Termine.route, selectedRoute, onNavigate)
|
||||
CompactLink("Spielplan", Destinations.Spielplan.route, selectedRoute, onNavigate)
|
||||
CompactLink("Newsletter", Destinations.NewsletterSubscribe.route, selectedRoute, onNavigate)
|
||||
if (navigationState.showGallery) {
|
||||
CompactLink("Galerie", Destinations.Gallery.route, selectedRoute, onNavigate)
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier.horizontalScroll(mainScroll),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
CompactLink("Start", Destinations.Home.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||
CompactSectionLink("Verein", MenuSection.VEREIN, section) { sectionOverride.value = MenuSection.VEREIN }
|
||||
CompactSectionLink("Mannschaften", MenuSection.MANNSCHAFTEN, section) { sectionOverride.value = MenuSection.MANNSCHAFTEN }
|
||||
CompactSectionLink("Training", MenuSection.TRAINING, section) { sectionOverride.value = MenuSection.TRAINING }
|
||||
CompactLink("Termine", Destinations.Termine.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||
CompactLink("Spielplan", Destinations.Spielplan.route, selectedRoute, onNavigate) { sectionOverride.value = MenuSection.MANNSCHAFTEN }
|
||||
CompactSectionLink("Newsletter", MenuSection.NEWSLETTER, section) { sectionOverride.value = MenuSection.NEWSLETTER }
|
||||
if (navigationState.showGallery) {
|
||||
CompactLink("Galerie", Destinations.Gallery.route, selectedRoute, onNavigate) { sectionOverride.value = MenuSection.VEREIN }
|
||||
}
|
||||
if (navigationState.loggedIn) {
|
||||
CompactSectionLink("Intern", MenuSection.INTERN, section) { sectionOverride.value = MenuSection.INTERN }
|
||||
}
|
||||
CompactLink("Kontakt", Destinations.Contact.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||
if (navigationState.isAdmin || navigationState.canAccessNewsletter || navigationState.canAccessContactRequests) {
|
||||
CompactSectionLink("CMS", MenuSection.INTERN, section) { sectionOverride.value = MenuSection.INTERN }
|
||||
}
|
||||
}
|
||||
if (navigationState.loggedIn) {
|
||||
CompactLink("Intern", Destinations.MemberArea.route, selectedRoute, onNavigate)
|
||||
|
||||
if (mainScroll.canScrollBackward) {
|
||||
Text(
|
||||
"◀",
|
||||
color = Color(0xFFD4D4D8),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.background(Color(0x66000000), RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
CompactLink("Kontakt", Destinations.Contact.route, selectedRoute, onNavigate)
|
||||
if (navigationState.isAdmin || navigationState.canAccessNewsletter || navigationState.canAccessContactRequests) {
|
||||
CompactLink("CMS", Destinations.Cms.route, selectedRoute, onNavigate)
|
||||
if (mainScroll.canScrollForward) {
|
||||
Text(
|
||||
"▶",
|
||||
color = Color(0xFFD4D4D8),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.background(Color(0x66000000), RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +142,7 @@ private fun CompactNavigation(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.horizontalScroll(subScroll)
|
||||
.padding(top = 3.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
@@ -126,12 +158,15 @@ private fun CompactNavigation(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (subItems.isNotEmpty()) {
|
||||
ScrollHintRow(subScroll)
|
||||
}
|
||||
|
||||
if (cmsExpanded.value && cmsChildren.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.horizontalScroll(cmsSubScroll)
|
||||
.padding(top = 6.dp, bottom = 3.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
@@ -139,6 +174,31 @@ private fun CompactNavigation(
|
||||
SubLink(child.label, child.route == selectedRoute) { onNavigate(child.route) }
|
||||
}
|
||||
}
|
||||
ScrollHintRow(cmsSubScroll)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CompactSectionLink(
|
||||
label: String,
|
||||
section: MenuSection,
|
||||
activeSection: MenuSection?,
|
||||
modifier: Modifier = Modifier,
|
||||
onSelect: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
color = if (activeSection == section) Primary600 else Color.Transparent,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = modifier.clickable { onSelect() },
|
||||
) {
|
||||
Text(
|
||||
label,
|
||||
color = if (activeSection == section) Color.White else Color(0xFFD4D4D8),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(vertical = 9.dp, horizontal = 2.dp),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,11 +345,15 @@ private fun CompactLink(
|
||||
selectedRoute: String?,
|
||||
onNavigate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
beforeNavigate: () -> Unit = {},
|
||||
) {
|
||||
Surface(
|
||||
color = if (route == selectedRoute) Primary600 else Color.Transparent,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = modifier.clickable { onNavigate(route) },
|
||||
modifier = modifier.clickable {
|
||||
beforeNavigate()
|
||||
onNavigate(route)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
label,
|
||||
@@ -302,6 +366,30 @@ private fun CompactLink(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScrollHintRow(scrollState: ScrollState) {
|
||||
if (!scrollState.canScrollBackward && !scrollState.canScrollForward) return
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 2.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
if (scrollState.canScrollBackward) "◀" else "",
|
||||
color = Color(0xFFD4D4D8),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
Text(
|
||||
if (scrollState.canScrollForward) "▶" else "",
|
||||
color = Color(0xFFD4D4D8),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubLink(label: String, selected: Boolean, onClick: () -> Unit) {
|
||||
Surface(
|
||||
|
||||
@@ -5,11 +5,11 @@ org.gradle.workers.max=2
|
||||
LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
|
||||
|
||||
# Production backend for Play Store build variant
|
||||
PRODUCTION_API_BASE_URL=https://harheimertc.tsschulz.de/
|
||||
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
||||
|
||||
# Android app versioning for Play Store uploads
|
||||
ANDROID_VERSION_CODE=17
|
||||
ANDROID_VERSION_NAME=0.9.12
|
||||
ANDROID_VERSION_CODE=18
|
||||
ANDROID_VERSION_NAME=0.9.13
|
||||
|
||||
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
||||
RELEASE_MINIFY_ENABLED=false
|
||||
|
||||
Reference in New Issue
Block a user