Bimba.git

commit 40f1d218cc3dc0d93ea07974376e578967b24320

Author: Adam Evyčędo <git@apiote.xyz>

select basic options for journeys

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8b27f32a4ad2ff9e1d791d1c289cbb213be32dc8..41f9609d54c86b09821831f26252f2a83173b254 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,3 @@
-
 import com.android.build.gradle.internal.tasks.factory.dependsOn
 import com.android.build.gradle.tasks.ExtractDeepLinksTask
 import com.android.build.gradle.tasks.MergeResources
@@ -32,7 +31,7 @@ 		versionCode = 34
 		versionName = "3.8.0"
 
 		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-		resourceConfigurations += listOf("en", "de", "en-rGB", "en-rUS", "et", "fr", "it", "pl")
+		resourceConfigurations += listOf("en", "de", "en-rGB", "en-rUS", "et", "fr", "it", "pl") // TODO ru, ta
 	}
 
 	buildTypes {
@@ -125,6 +124,8 @@ 	implementation("ch.acra:acra-notification:5.12.0")
 	implementation("com.squareup.okhttp3:okhttp:4.12.0")
 	implementation("com.squareup.moshi:moshi-kotlin:1.15.2")
 	implementation("androidx.activity:activity:1.9.3")
+	implementation("com.google.maps.android:maps-ktx:5.1.1")
+	implementation("com.google.maps.android:maps-utils-ktx:5.1.1")
 
 	coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
 




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousJourney.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousJourney.kt
index bd47fc72e1ba1bcaa102c8766b5930c268ad4b87..9a2eec19639badea4389b75735e648bbde2c93d1 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousJourney.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousJourney.kt
@@ -96,8 +96,8 @@ 					it.agencyName,
 					it.distance?.toDouble()?.let { Metre(it) },
 					Second(it.duration),
 					(it.intermediateStops ?: emptyList()).map { Place(it) },
-					/*it.legGeometry,
-					it.rental,
+					it.legGeometry,
+					/*it.rental,
 					it.steps,*/
 				)
 			}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/DashboardViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/DashboardViewModel.kt
index 6acbe27969f244d5917f458f5f24867189b522df..877dbb6656342cf073a915c8c42fe80389e580b7 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/DashboardViewModel.kt
@@ -10,7 +10,10 @@ import androidx.lifecycle.ViewModel
 import com.google.android.material.chip.Chip
 import com.google.android.material.textfield.TextInputEditText
 import xyz.apiote.bimba.czwek.repo.Place
+import xyz.apiote.bimba.czwek.repo.TimeReference
 import xyz.apiote.bimba.czwek.search.Query
+import java.time.LocalDate
+import java.time.LocalTime
 
 class DashboardViewModel : ViewModel() {
 	companion object {
@@ -57,5 +60,9 @@ 		DEST_KEY to null
 	)
 
 	val textInputs = mutableMapOf<String, TextInputEditText>()
+
+	var timeReference: TimeReference = TimeReference.DEPART_AFTER
+	var date: Long? = null
+	var time: LocalTime? = null
 
 }




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/journey/JourneyFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/journey/JourneyFragment.kt
index 3b7fe0587642546d30be3ebd49b23b9aec4bc173..ad046266f26f5a5fbe5ca3d90376e211172aa46f 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/journey/JourneyFragment.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/journey/JourneyFragment.kt
@@ -16,13 +16,16 @@ import android.os.Bundle
 import android.text.Editable
 import android.text.Spanned
 import android.text.TextWatcher
+import android.text.format.DateUtils
 import android.text.style.ImageSpan
 import android.util.Log
 import android.view.KeyEvent
 import android.view.LayoutInflater
+import android.view.Menu
 import android.view.MotionEvent.ACTION_UP
 import android.view.View
 import android.view.ViewGroup
+import android.widget.PopupMenu
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
@@ -34,6 +37,9 @@ import androidx.swiperefreshlayout.widget.CircularProgressDrawable
 import com.google.android.material.chip.Chip
 import com.google.android.material.chip.ChipDrawable
 import com.google.android.material.chip.ChipGroup
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.timepicker.MaterialTimePicker
+import com.google.android.material.timepicker.TimeFormat
 import com.google.openlocationcode.OpenLocationCode
 import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.dashboard.DashboardViewModel
@@ -41,11 +47,17 @@ import xyz.apiote.bimba.czwek.dashboard.MainActivity
 import xyz.apiote.bimba.czwek.databinding.FragmentJourneyBinding
 import xyz.apiote.bimba.czwek.dpToPixelI
 import xyz.apiote.bimba.czwek.journeys.JourneysActivity
+import xyz.apiote.bimba.czwek.repo.JourneyParams
 import xyz.apiote.bimba.czwek.repo.Place
 import xyz.apiote.bimba.czwek.repo.Position
+import xyz.apiote.bimba.czwek.repo.TimeReference
 import xyz.apiote.bimba.czwek.search.Query
 import xyz.apiote.bimba.czwek.search.Query.Mode
 import xyz.apiote.bimba.czwek.search.ResultsActivity
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalTime
+import java.time.ZoneId
 
 class JourneyFragment : Fragment(), LocationListener {
 	companion object {
@@ -152,6 +164,11 @@ 			val intent = JourneysActivity.getIntent(
 				requireContext(),
 				dashboard.viewModel.data[DashboardViewModel.ORIGIN_KEY]!!.value!!,
 				dashboard.viewModel.data[DashboardViewModel.DEST_KEY]!!.value!!,
+				JourneyParams(
+					dashboard.viewModel.timeReference,
+					dashboard.viewModel.date?.let { epochMilli -> Instant.ofEpochMilli(epochMilli) }?.atZone(ZoneId.systemDefault())?.toLocalDate(),
+					dashboard.viewModel.time
+				)
 			)
 			startActivity(intent)
 		}
@@ -214,7 +231,118 @@ 				afterTextChanged(DashboardViewModel.DEST_KEY, binding.destinationSuggestions, s, inflater)
 			}
 		})
 
+		setTimeReference(dashboard.viewModel.timeReference.id())
+		binding.chipTimeReference.setOnClickListener {
+			val menu = PopupMenu(requireContext(), it)
+			menu.menu.add(
+				Menu.NONE,
+				TimeReference.DEPART_AFTER.id(),
+				TimeReference.DEPART_AFTER.ordinal,
+				R.string.depart_after
+			)
+			menu.menu.add(
+				Menu.NONE,
+				TimeReference.ARRIVE_BY.id(),
+				TimeReference.ARRIVE_BY.ordinal,
+				R.string.arrive_by
+			)
+			menu.setOnMenuItemClickListener {
+				setTimeReference(it.itemId)
+				true
+			}
+			menu.show()
+		}
+
+		if (dashboard.viewModel.date != null) {
+			binding.chipDate.text =
+				DateUtils.formatDateTime(
+					context,
+					dashboard.viewModel.date!!,
+					DateUtils.FORMAT_SHOW_DATE
+				)
+		} else {
+			binding.chipDate.setText(R.string.today)
+		}
+		binding.chipDate.setOnClickListener { _ ->
+			MaterialDatePicker.Builder.datePicker().setTitleText(R.string.title_select_date_journey)
+				.setNegativeButtonText(R.string.clear_date_selection)
+				.apply {
+					if (dashboard.viewModel.date != null) {
+						setSelection(dashboard.viewModel.date!!)
+					}
+				}
+				.build()
+				.apply {
+					addOnNegativeButtonClickListener { _ ->
+						dashboard.viewModel.date = null
+						binding.chipDate.setText(R.string.today)
+					}
+					addOnPositiveButtonClickListener { t ->
+						dashboard.viewModel.date = t
+						binding.chipDate.text =
+							DateUtils.formatDateTime(context, t, DateUtils.FORMAT_SHOW_DATE)
+					}
+				}.show((activity as MainActivity).supportFragmentManager, null)
+		}
+
+
+		if (dashboard.viewModel.date != null) {
+			binding.chipTime.text =
+				DateUtils.formatDateTime(
+					context,
+					dashboard.viewModel.time!!.atDate(LocalDate.now()).atZone(ZoneId.systemDefault())
+						.toEpochSecond() * 1000,
+					DateUtils.FORMAT_SHOW_TIME
+				)
+		} else {
+			binding.chipTime.setText(R.string.now)
+		}
+		binding.chipTime.setOnClickListener { _ ->
+			val picker = MaterialTimePicker.Builder().setTitleText(R.string.title_select_time_journey)
+				.setTimeFormat(TimeFormat.CLOCK_24H)
+				.setNegativeButtonText(R.string.clear_date_selection)
+				.apply {
+					if (dashboard.viewModel.time != null) {
+						setHour(dashboard.viewModel.time!!.hour)
+						setMinute(dashboard.viewModel.time!!.minute)
+					} else {
+						setHour(LocalTime.now().hour)
+						setMinute(LocalTime.now().minute)
+					}
+				}.build()
+
+			picker.apply {
+				addOnNegativeButtonClickListener { _ ->
+					dashboard.viewModel.time = null
+					binding.chipTime.setText(R.string.now)
+				}
+				addOnPositiveButtonClickListener { _ ->
+					dashboard.viewModel.time = LocalTime.of(picker.hour, picker.minute)
+					binding.chipTime.text =
+						DateUtils.formatDateTime(
+							context,
+							dashboard.viewModel.time!!.atDate(LocalDate.now()).atZone(ZoneId.systemDefault())
+								.toEpochSecond() * 1000,
+							DateUtils.FORMAT_SHOW_TIME
+						)
+				}
+			}.show((activity as MainActivity).supportFragmentManager, null)
+		}
 		return root
+	}
+
+	private fun setTimeReference(option: Int) {
+		when (option) {
+			TimeReference.DEPART_AFTER.id() -> {
+				binding.chipTimeReference.setText(R.string.depart_after)
+				dashboard.viewModel.timeReference = TimeReference.DEPART_AFTER
+			}
+
+			TimeReference.ARRIVE_BY.id() -> {
+				binding.chipTimeReference.setText(R.string.arrive_by)
+				dashboard.viewModel.timeReference = TimeReference.ARRIVE_BY
+			}
+		}
 	}
 
 	private fun afterTextChanged(




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt
index 5f9707b1c1f5a80b54d9c20dc1ee9d4753f9216e..89291eb7541d2ab0989fc5e0f2b18f0ed861a7a7 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt
@@ -9,23 +9,32 @@ import android.content.Intent
 import android.content.res.Configuration.UI_MODE_NIGHT_MASK
 import android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED
 import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.DashPathEffect
 import android.os.Build
 import android.os.Bundle
 import android.view.View
 import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.lifecycle.ViewModelProvider
 import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.maps.android.PolyUtil
 import org.osmdroid.tileprovider.tilesource.TileSourceFactory
 import org.osmdroid.util.BoundingBox
+import org.osmdroid.util.GeoPoint
 import org.osmdroid.views.CustomZoomButtonsController
+import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.Polyline
 import org.osmdroid.views.overlay.TilesOverlay
 import org.osmdroid.views.overlay.gestures.RotationGestureOverlay
+import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.databinding.ActivityJourneysBinding
 import xyz.apiote.bimba.czwek.dpToPixelI
+import xyz.apiote.bimba.czwek.repo.JourneyParams
+import xyz.apiote.bimba.czwek.repo.LineType
 import xyz.apiote.bimba.czwek.repo.Place
 import kotlin.math.max
 import kotlin.math.min
@@ -36,14 +45,17 @@
 	companion object {
 		const val ORIGIN_PARAM = "origin"
 		const val DESTINATION_PARAM = "destination"
+		const val PARAMS_PARAM = "params"
 
 		fun getIntent(
 			context: Context,
 			origin: Place,
 			destination: Place,
+			params: JourneyParams
 		) = Intent(context, JourneysActivity::class.java).apply {
 			putExtra(ORIGIN_PARAM, origin)
 			putExtra(DESTINATION_PARAM, destination)
+			putExtra(PARAMS_PARAM, params)
 		}
 	}
 
@@ -79,7 +91,7 @@ 		}
 
 		binding.map.setTileSource(TileSourceFactory.MAPNIK)
 		if (((resources?.configuration?.uiMode ?: UI_MODE_NIGHT_UNDEFINED)
-					and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES
+				and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES
 		) {
 			binding.map.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
 		}
@@ -103,8 +115,45 @@ 				binding.emptyImage.visibility = View.VISIBLE
 			} else {
 				binding.journeys.visibility = View.VISIBLE
 				binding.journeys.adapter = JourneysAdapter(layoutInflater, this, it) {
+					binding.map.overlays.removeAll { true }
+
+					val originMarker = Marker(binding.map).apply {
+						position = GeoPoint(it.legs[0].origin.latitude, it.legs[0].origin.longitude)
+						setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+						val scale = binding.map.zoomLevelDouble / -4 + 5.5
+						icon = AppCompatResources.getDrawable(
+							this@JourneysActivity,
+							R.drawable.pin // R.drawable.legOrigin
+						) // TODO scale and colour
+					}
+					binding.map.overlays.add(originMarker)
+
+					val destinationMarker = Marker(binding.map).apply {
+						position = GeoPoint(it.legs[0].origin.latitude, it.legs[0].origin.longitude)
+						setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+						val scale = binding.map.zoomLevelDouble / -4 + 5.5
+						icon = AppCompatResources.getDrawable(
+							this@JourneysActivity,
+							R.drawable.pin //R.drawable.legDestination
+						) // TODO scale and colour
+					}
+					binding.map.overlays.add(destinationMarker)
+
+					it.legs.forEachIndexed { i, leg ->
+						val shapePoints = PolyUtil.decode(leg.shape.points).map {
+							GeoPoint(it.latitude, it.longitude)
+						}
+						val shape = Polyline()
+						val paint = shape.outlinePaint
+						paint.color = leg.start.vehicle.Line.colour.toInt() // TODO contrast
+						if (leg.start.vehicle.Line.kind == LineType.WALK) { // TODO and other active
+							paint.setPathEffect(DashPathEffect(floatArrayOf(10f, 10f), 0f))
+						}
+						shape.setPoints(shapePoints)
+						binding.map.overlays.add(shape)
+					}
+					binding.map.invalidate()
 					// todo move map accordingly
-					// todo show shape on map
 				}
 			}
 		}
@@ -119,7 +168,7 @@
 	fun zoomMap(margin: Int = 0, box: BoundingBox? = null) {
 		val origin = getOrigin()
 		val destination = getDestination()
-		val bb = box?:BoundingBox(
+		val bb = box ?: BoundingBox(
 			max(origin.latitude, destination.latitude),
 			max(origin.longitude, destination.longitude),
 			min(origin.latitude, destination.latitude),




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt
index fe259f7d7cbf3b9822d9bc4dbb3f5575e48f9d13..0446e96f18431cc2725c62469f36fe8ab25fc40f 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt
@@ -6,6 +6,7 @@ package xyz.apiote.bimba.czwek.repo
 
 import android.os.Parcelable
 import kotlinx.parcelize.Parcelize
+import xyz.apiote.bimba.czwek.api.transitous.model.EncodedPolyline
 import xyz.apiote.bimba.czwek.api.transitous.model.Place
 import xyz.apiote.bimba.czwek.units.DistanceUnit
 import xyz.apiote.bimba.czwek.units.TimeUnit
@@ -22,8 +23,8 @@ 	val agencyName: String?,
 	val distance: DistanceUnit?,
 	val duration: TimeUnit,
 	val intermediateStops: List<xyz.apiote.bimba.czwek.repo.Place>,
+	val shape: EncodedPolyline,  // TODO to list of points
 	/* TODO
-	val shape: EncodedPolyline,
 	val rental: Rental?,
 	val steps: List<StepInstruction>?,*/
 )




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/JourneyParams.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/JourneyParams.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bf6d60f1e2d1573b10dce5d35e986ca16abd62ec
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/JourneyParams.kt
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Adam Evyčędo
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package xyz.apiote.bimba.czwek.repo
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+import java.time.LocalDate
+import java.time.LocalTime
+
+
+enum class TimeReference {
+	DEPART_AFTER, ARRIVE_BY;
+
+	fun id(): Int {
+		return when (this) {
+			DEPART_AFTER -> 1
+			ARRIVE_BY -> 2
+		}
+	}
+}
+
+@Parcelize
+data class JourneyParams(val timeReference: TimeReference, val date: LocalDate?, val time: LocalTime?) : Parcelable
+/*
+					date ?: Instant.now().atZone(ZoneId.systemDefault()).toLocalDate()
+					time ?: Instant.now().atZone(ZoneId.systemDefault()).toLocalTime()
+*/
\ No newline at end of file




diff --git a/app/src/main/res/layout/fragment_journey.xml b/app/src/main/res/layout/fragment_journey.xml
index 649a53bbc3018e4aaf0ede4e62a3e1d7161495d6..568d127756bb13775605356cb572a6d9f4cf5484 100644
--- a/app/src/main/res/layout/fragment_journey.xml
+++ b/app/src/main/res/layout/fragment_journey.xml
@@ -142,12 +142,12 @@ 		app:layout_constraintEnd_toEndOf="parent"
 		app:layout_constraintStart_toStartOf="parent"
 		app:layout_constraintTop_toBottomOf="@+id/materialDivider">
 
-		<!-- on click menu: https://stackoverflow.com/a/67764469 -->
 		<com.google.android.material.chip.Chip
 			android:id="@+id/chip_time_reference"
 			style="@style/Widget.Material3.Chip.Filter"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
+			android:checkable="false"
 			android:text="@string/depart_after"
 			app:checkedIconEnabled="false"
 			app:closeIcon="@drawable/dropdown"
@@ -159,6 +159,7 @@ 			android:id="@+id/chip_date"
 			style="@style/Widget.Material3.Chip.Filter"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
+			android:checkable="false"
 			android:text="@string/today"
 			app:checkedIconEnabled="false"
 			app:closeIcon="@drawable/dropdown"
@@ -170,6 +171,7 @@ 			android:id="@+id/chip_time"
 			style="@style/Widget.Material3.Chip.Filter"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
+			android:checkable="false"
 			android:text="@string/now"
 			app:checkedIconEnabled="false"
 			app:closeIcon="@drawable/dropdown"




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8bbbfa15f1a140cd61743fc3b96b8dbabc6498c5..2530fb270cad59d4a51f216f271bcb98cc88904a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -313,4 +313,6 @@ 	use as origin
 	<string name="use_as_destination">use as destination</string>
 	<string name="here">here</string>
 	<string name="no_journeys_found">No journeys found</string>
+	<string name="title_select_date_journey">Select date of the journey</string>
+	<string name="title_select_time_journey">Select journey time</string>
 </resources>




diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
index 722359f1e44be468bf3f3c1b85e5c959de1db869..8d60ef8d598857721c521b96aa430bf03311e0aa 100644
--- a/app/src/main/res/values-en-rGB/strings.xml
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -292,4 +292,6 @@ 	approx. dep.
 	<string name="departure">departure</string>
 	<string name="approximately">approximately</string>
     <string name="depart_after">Depart after</string>
+	<string name="title_select_date_journey">Select date of the journey</string>
+	<string name="title_select_time_journey">Select journey time</string>
 </resources>




diff --git a/app/src/main/res/values-en-rUS/strings.xml b/app/src/main/res/values-en-rUS/strings.xml
index 8c8cbd4b80127310143ac5cf40cb52c6839db3cd..682a4d16fca887b28b763a9c5638269edfcd1a53 100644
--- a/app/src/main/res/values-en-rUS/strings.xml
+++ b/app/src/main/res/values-en-rUS/strings.xml
@@ -290,4 +290,6 @@ 	approx. dep.
 	<string name="departure">departure</string>
 	<string name="approximately">approximately</string>
     <string name="depart_after">Depart after</string>
+	<string name="title_select_date_journey">Select date of the journey</string>
+	<string name="title_select_time_journey">Select journey time</string>
 </resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6963c8fc70430231237b20797a4b11eb06a2e993..059707a3db97ccd1a5cca90ae4651649260decd4 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -1,4 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+SPDX-FileCopyrightText: Adam Evyčędo and contributors using Weblate
+
+SPDX-License-Identifier: GPL-3.0-or-later
+-->
 <resources>
     <string name="error_offline">Вы находитесь вне сети. Подключитесь к Интернету</string>
     <string name="error_429">Превышен лимит запросов. Попробуйте снова позже.</string>




diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 689b6c6b21d2fe903111edb5134e62f730e974a3..dbe162f809d28e80e67b8ef1d8e0027e7edf1c6e 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -1,4 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+SPDX-FileCopyrightText: Adam Evyčędo and contributors using Weblate
+
+SPDX-License-Identifier: GPL-3.0-or-later
+-->
 <resources>
     <plurals name="distance_in_yd_cd">
         <item quantity="one">%1$d முற்றத்தில்</item>




diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index a6b3daec9354f9ae75cdf8d94a67446c6227dd96..0009fe182a89b8cd7a68418e441239b391e8370d 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -1,2 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+SPDX-FileCopyrightText: Adam Evyčędo and contributors using Weblate
+
+SPDX-License-Identifier: GPL-3.0-or-later
+-->
 <resources></resources>
\ No newline at end of file




diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
index c5a157e555021e64b2eb8268a1a74397eb19311e..a955042c052f815ba91f04b8ef50e8a3831ca6b7 100644
--- a/app/src/main/res/xml/locales_config.xml
+++ b/app/src/main/res/xml/locales_config.xml
@@ -15,4 +15,6 @@     
     <locale android:name="fr"/>
     <locale android:name="it"/>
     <locale android:name="pl"/>
+    <!-- TODO <locale android:name="ru"/> -->
+    <!-- TODO <locale android:name="ta"/> -->
 </locale-config>
\ No newline at end of file