Author: Adam <git@apiote.xyz>
[wip] search journeys
%!v(PANIC=String method: strings: negative Repeat count)
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 new file mode 100644 index 0000000000000000000000000000000000000000..819fae616a88170d90479a661ab858448e44f5e8 --- /dev/null +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/DashboardViewModel.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Adam Evyčędo +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package xyz.apiote.bimba.czwek.dashboard + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import xyz.apiote.bimba.czwek.repo.Place + +class DashboardViewModel : ViewModel() { + + private val _origin = MutableLiveData<Place>() + val origin: LiveData<Place> = _origin + + fun setOrigin(o: Place) { + _origin.value = o + } + + private val _destination = MutableLiveData<Place>() + val destination: LiveData<Place> = _destination + + fun setDestination(d: Place) { + _destination.value = d + } + +} diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt index ad4d49d02249fe8df2290fa316556d0aaaf7d9a2..0ac2110a07cbac7e22c698867779985daae5ff9e 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt @@ -7,6 +7,7 @@ import android.Manifest import android.content.Intent import android.content.pm.PackageManager +import android.content.res.TypedArray import android.os.Build import android.os.Bundle import android.view.View @@ -27,6 +28,7 @@ import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks +import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager @@ -53,12 +55,16 @@ private lateinit var locationPermissionRequest: ActivityResultLauncher> private lateinit var permissionAsker: Fragment private var locationPermissionDialogShown = false + private var lastFragment: Fragment? = null + lateinit var viewModel: DashboardViewModel override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + + viewModel = ViewModelProvider(this)[DashboardViewModel::class.java] FirstRunActivity.setFirstRunDone(this) @@ -191,7 +197,7 @@ binding.container.openDrawer(binding.navigationDrawer) } } - fun onGpsClicked(fragment: Fragment) { + fun onGpsClicked(fragment: Fragment): Boolean { when (PackageManager.PERMISSION_GRANTED) { ContextCompat.checkSelfPermission( this, @@ -205,7 +211,12 @@ is MapFragment -> { fragment.showLocation() } + + is JourneyFragment -> { + + } } + return true } else -> { @@ -216,6 +227,7 @@ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ) + return false } } } @@ -250,7 +262,7 @@ text.toString() ) } .show() - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit{ + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { putBoolean(NO_GEOCODING_DATA_SHOWN, true) } } @@ -267,7 +279,15 @@ /* todo [3.2] (ux,low) animation https://developer.android.com/guide/fragments/animate https://github.com/raheemadamboev/fab-explosion-animation-app */ - startActivity(ResultsActivity.getIntent(this, ResultsActivity.Mode.MODE_POSITION, query, centerLatitude, centerLongitude)) + startActivity( + ResultsActivity.getIntent( + this, + ResultsActivity.Mode.MODE_POSITION, + query, + centerLatitude, + centerLongitude + ) + ) } private fun showResults(mode: ResultsActivity.Mode, query: String = "") { @@ -278,25 +298,53 @@ */ startActivity(ResultsActivity.getIntent(this, mode, query)) } - private fun setNavbarIcons(f: Fragment) { + fun showBadge(complete: Boolean = false) { + val colourID = if (complete) { + com.google.android.material.R.attr.colorPrimary + } else { + com.google.android.material.R.attr.colorOnSurfaceVariant + } + val badge = binding.bottomNavigation.getOrCreateBadge(R.id.navigation_journey) + val a: TypedArray = theme.obtainStyledAttributes( + R.style.Theme_Bimba, intArrayOf(colourID) + ) + val colour = a.getColor(0, 0) + a.recycle() + badge.backgroundColor = colour + badge.isVisible = true + } + + fun hideBadge() { + val badge = binding.bottomNavigation.getBadge(R.id.navigation_journey) + badge?.isVisible = false + } + + private fun setNavbarIcons(f: Fragment?) { binding.bottomNavigation.menu[2].setIcon(R.drawable.journey_outline) binding.bottomNavigation.menu[1].setIcon(R.drawable.home_outline) binding.bottomNavigation.menu[0].setIcon(R.drawable.map_outline) when (f) { is HomeFragment -> { binding.bottomNavigation.menu[1].setIcon(R.drawable.home_black) + lastFragment = f } is JourneyFragment -> { binding.bottomNavigation.menu[2].setIcon(R.drawable.journey_black) + lastFragment = f } is MapFragment -> { binding.bottomNavigation.menu[0].setIcon(R.drawable.map_black) + lastFragment = f + } + + null -> { + binding.bottomNavigation.menu[1].setIcon(R.drawable.home_black) } else -> { - binding.bottomNavigation.menu[1].setIcon(R.drawable.home_black) + setNavbarIcons(lastFragment) } } } 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 544558c0bcfc8f8cf128054e8e2f6daa9fcd17cb..b9a79811cceff3ef625fb7b1554a794a9c15e77a 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 @@ -4,24 +4,44 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.dashboard.ui.journey +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.RectF +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager import android.os.Bundle +import android.text.Spanned +import android.text.style.ImageSpan +import android.util.Log import android.view.LayoutInflater +import android.view.MotionEvent.ACTION_UP import android.view.View import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipDrawable +import com.google.android.material.textfield.TextInputEditText +import xyz.apiote.bimba.czwek.R +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.ChangeOption import xyz.apiote.bimba.czwek.repo.Place -import xyz.apiote.bimba.czwek.repo.Position -import xyz.apiote.bimba.czwek.repo.Stop -class JourneyFragment : Fragment() { +class JourneyFragment : Fragment(), LocationListener { private var _binding: FragmentJourneyBinding? = null private val binding get() = _binding!! + private lateinit var dashboard: MainActivity + private var hereChipRequester: Chip? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -30,29 +50,184 @@ ): View { val journeyViewModel = ViewModelProvider(this)[JourneyViewModel::class.java] + // TODO separate layout: two columns for horizontal + _binding = FragmentJourneyBinding.inflate(inflater, container, false) val root: View = binding.root - val intent = JourneysActivity.getIntent( - requireContext(), - Place( - Stop("", "", "", "", "", Position(0.0, 0.0), emptyList<ChangeOption>(), ""), - 52.402815, - 16.911795 - ), - Place( - Stop("", "", "", "", "", Position(0.0, 0.0), emptyList<ChangeOption>(), ""), - 52.445433, - 17.079231 + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + binding.originInput.updatePadding(left = insets.left, right = insets.right) + binding.originInput.updateLayoutParams<ViewGroup.MarginLayoutParams> { + topMargin = insets.top + dpToPixelI(16f) + windowInsets.displayCutout?.safeInsetLeft?.let { + leftMargin = it + dpToPixelI(16f) + } + windowInsets.displayCutout?.safeInsetRight?.let { + rightMargin = it + dpToPixelI(16f) + } + } + binding.originSuggestions.updatePadding(left = insets.left, right = insets.right) + binding.originSuggestions.updateLayoutParams<ViewGroup.MarginLayoutParams> { + windowInsets.displayCutout?.safeInsetLeft?.let { + leftMargin = it + dpToPixelI(16f) + } + windowInsets.displayCutout?.safeInsetRight?.let { + rightMargin = it + dpToPixelI(16f) + } + } + binding.destinationInput.updatePadding(left = insets.left, right = insets.right) + binding.destinationInput.updateLayoutParams<ViewGroup.MarginLayoutParams> { + windowInsets.displayCutout?.safeInsetLeft?.let { + leftMargin = it + dpToPixelI(16f) + } + windowInsets.displayCutout?.safeInsetRight?.let { + rightMargin = it + dpToPixelI(16f) + } + } + binding.destinationSuggestions.updatePadding(left = insets.left, right = insets.right) + binding.destinationSuggestions.updateLayoutParams<ViewGroup.MarginLayoutParams> { + windowInsets.displayCutout?.safeInsetLeft?.let { + leftMargin = it + dpToPixelI(16f) + } + windowInsets.displayCutout?.safeInsetRight?.let { + rightMargin = it + dpToPixelI(16f) + } + } + windowInsets + } + + dashboard = activity as MainActivity + + dashboard.hideBadge() + dashboard.viewModel.origin.let { + chipifyOrigin(it.value) + } + + dashboard.viewModel.destination.let { + chipifyDestination(it.value) + } + + binding.originChipHere.setOnClickListener { + setHere(it as Chip) + } + + binding.destinationChipHere.setOnClickListener { + setHere(it as Chip) + } + + binding.goButton.isEnabled = + dashboard.viewModel.origin.value != null && dashboard.viewModel.destination.value != null + + binding.goButton.setOnClickListener { + val intent = JourneysActivity.getIntent( + requireContext(), + dashboard.viewModel.origin.value!!, + dashboard.viewModel.destination.value!! ) - ) - startActivity(intent) + startActivity(intent) + } + + dashboard.viewModel.origin.observe(viewLifecycleOwner) { + chipifyOrigin(it) + } + + dashboard.viewModel.destination.observe(viewLifecycleOwner) { + chipifyDestination(it) + } + + // TODO listen to text changes + // TODO search by text input return root } + private fun chipifyOrigin(place: Place?) { + if (place != null) { + chipify(place, binding.origin) + } + } + + private fun chipifyDestination(place: Place?) { + if (place != null) { + chipify(place, binding.destination) + } + } + + private fun chipify(place: Place, textView: TextInputEditText) { + var chip: ChipDrawable? = ChipDrawable.createFromResource(requireContext(), R.xml.journey_chip) + val text = place.shortString() + textView.setText(text) + chip!!.text = text + chip.setBounds(0, 0, chip.intrinsicWidth, chip.intrinsicHeight) + val span = ImageSpan(chip) + textView.text?.setSpan(span, 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + @SuppressLint("ClickableViewAccessibility") + textView.setOnTouchListener { v, e -> + v.performClick() + if (chip != null) { + val chipContentRect = RectF() + val chipCloseRect = RectF() + chip!!.getChipTouchBounds(chipContentRect) + chip!!.getCloseIconTouchBounds(chipCloseRect) + if (e.x > textView.totalPaddingLeft && e.x < textView.totalPaddingLeft + chipContentRect.right + && e.y > textView.totalPaddingTop && e.y < textView.totalPaddingTop + chipContentRect.bottom + ) { + if (e.action == ACTION_UP) { + // TODO popup + Log.i("Touch", "content") + } + true + } else if (e.x > textView.totalPaddingLeft + chipContentRect.right && e.x < textView.totalPaddingLeft + chipContentRect.right + chipCloseRect.right + && e.y > textView.totalPaddingTop && e.y < textView.totalPaddingTop + chipCloseRect.bottom + ) { + if (e.action == ACTION_UP) { + textView.setText("") + chip = null + } + true + } else { + false + } + } else { + false + } + } + } + + private fun setHere(v: Chip) { + hereChipRequester = v + if (dashboard.onGpsClicked(this)) { + try { + val locationManager = + requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, 1000 * 60 * 10, 100f, this + ) + locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) + ?.let { onLocationChanged(it) } + } catch (_: SecurityException) { + Log.wtf( + "locate", + "this shouldn’t happen because we don’t run this without location permission" + ) + } + } + } + override fun onDestroyView() { super.onDestroyView() _binding = null + } + + override fun onLocationChanged(location: Location) { + when (hereChipRequester?.id) { + R.id.origin_chip_here -> + dashboard.viewModel.setOrigin(Place(location.latitude, location.longitude)) + + R.id.destination_chip_here -> + dashboard.viewModel.setDestination(Place(location.latitude, location.longitude)) + } + hereChipRequester = null } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt index 65339fd2b8ef9eed82359fd09bd00e3bea0bce44..84abcd97aaf73ca976eba8b0614e9ae59e24d8df 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt @@ -34,12 +34,14 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.google.android.material.snackbar.Snackbar import org.osmdroid.config.Configuration +import org.osmdroid.events.MapEventsReceiver import org.osmdroid.events.MapListener import org.osmdroid.events.ScrollEvent import org.osmdroid.events.ZoomEvent import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.overlay.MapEventsOverlay import org.osmdroid.views.overlay.Marker import org.osmdroid.views.overlay.TilesOverlay import org.osmdroid.views.overlay.gestures.RotationGestureOverlay @@ -50,6 +52,7 @@ import xyz.apiote.bimba.czwek.dashboard.MainActivity import xyz.apiote.bimba.czwek.databinding.FragmentMapBinding import xyz.apiote.bimba.czwek.dpToPixelI import xyz.apiote.bimba.czwek.repo.ErrorLocatable +import xyz.apiote.bimba.czwek.repo.Place import xyz.apiote.bimba.czwek.repo.Position import xyz.apiote.bimba.czwek.repo.Stop import xyz.apiote.bimba.czwek.repo.Vehicle @@ -62,6 +65,7 @@ const val PREFERENCES_NAME = "shp" const val ZOOM_KEY = "mapZoom" const val CENTRE_LATITUDE_KEY = "mapCentreLat" const val CENTRE_LONGITUDE_KEY = "mapCentreLon" + const val PLACE_MARKER = "PLACE" } private var maybeBinding: FragmentMapBinding? = null @@ -74,6 +78,7 @@ private val handler = Handler(Looper.getMainLooper()) private var workRunnable = Runnable {} private var snack: Snackbar? = null + private lateinit var placeMarker: Marker @SuppressLint("ClickableViewAccessibility") override fun onCreateView( @@ -133,6 +138,38 @@ return true } }) + + placeMarker = Marker(binding.map) + placeMarker.id = PLACE_MARKER + placeMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + placeMarker.icon = AppCompatResources.getDrawable(requireContext(), R.drawable.pin) + + binding.map.overlays.add(MapEventsOverlay(object : MapEventsReceiver { + override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean { + return false + } + + override fun longPressHelper(p: GeoPoint?): Boolean { + if (p == null) { + return false + } + binding.map.overlays.remove(placeMarker) + placeMarker.position = p + binding.map.overlays.add(placeMarker) + binding.map.invalidate() + + val s = PlaceBottomSheet(requireContext(), Place(p.latitude, p.longitude)) {positionUsed -> + if (positionUsed) { + (activity as MainActivity).showBadge() + } + binding.map.overlays.remove(placeMarker) + binding.map.invalidate() + } + s.show((activity as MainActivity).supportFragmentManager, PlaceBottomSheet.TAG) + return true + } + })) + binding.map.setOnTouchListener { _, _ -> binding.floatingActionButton.show() false @@ -188,7 +225,7 @@ private fun observeLocatables() { mapViewModel.locatables.observe(viewLifecycleOwner) { binding.map.overlays.removeAll { marker -> - marker is Marker + marker is Marker && marker.id != PLACE_MARKER } if (it.size == 1 && it[0] is ErrorLocatable) { diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt index ceeb223f0fc4e4f66c0e9c81fda2a79c08c586f0..cd4df83f11c6a696a01e19ddba00fffaedadc2da 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt @@ -6,6 +6,7 @@ package xyz.apiote.bimba.czwek.dashboard.ui.map import android.content.ActivityNotFoundException import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle @@ -26,11 +27,13 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.launch import org.osmdroid.views.MapView import xyz.apiote.bimba.czwek.R +import xyz.apiote.bimba.czwek.dashboard.MainActivity import xyz.apiote.bimba.czwek.departures.DeparturesActivity import xyz.apiote.bimba.czwek.repo.CongestionLevel import xyz.apiote.bimba.czwek.repo.Locatable import xyz.apiote.bimba.czwek.repo.OccupancyStatus import xyz.apiote.bimba.czwek.repo.OnlineRepository +import xyz.apiote.bimba.czwek.repo.Place import xyz.apiote.bimba.czwek.repo.Position import xyz.apiote.bimba.czwek.repo.Stop import xyz.apiote.bimba.czwek.repo.TrafficResponseException @@ -57,6 +60,39 @@ } } } +class PlaceBottomSheet(private val context: Context, private val place: Place, private val onDismiss: (Boolean) -> Unit) : BottomSheetDialogFragment() { + companion object { + const val TAG = "PlaceBottomSheet" + } + + private var positionUsed = false + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.place_bottom_sheet, container, false) + view.findViewById<TextView>(R.id.coordinates).text = "${place.latitude}, ${place.longitude}" + view.findViewById<Button>(R.id.use_as_origin).setOnClickListener { + (activity as MainActivity).viewModel.setOrigin(place) + positionUsed = true + dismiss() + } + view.findViewById<Button>(R.id.use_as_destination).setOnClickListener { + (activity as MainActivity).viewModel.setDestination(place) + positionUsed = true + dismiss() + } + return view + } + + override fun onDismiss(dialog: DialogInterface) { + onDismiss(positionUsed) + super.onDismiss(dialog) + } +} + class MapBottomSheet(private val locatable: Locatable) : BottomSheetDialogFragment() { companion object { const val TAG = "MapBottomSheet" @@ -168,7 +204,15 @@ private fun showStop(content: View, stop: Stop) { context?.let { ctx -> content.findViewById<TextView>(R.id.stop_name).text = stop.name content.findViewById<Button>(R.id.departures_button).setOnClickListener { - startActivity(DeparturesActivity.getIntent(requireContext(), stop.code, stop.name, stop.feedID!!, true)) + startActivity( + DeparturesActivity.getIntent( + requireContext(), + stop.code, + stop.name, + stop.feedID!!, + true + ) + ) } content.findViewById<Button>(R.id.navigation_button).setOnClickListener { try { @@ -181,6 +225,18 @@ ) } catch (_: ActivityNotFoundException) { Toast.makeText(context, ctx.getString(R.string.no_map_app), Toast.LENGTH_SHORT).show() } + } + + content.findViewById<Button>(R.id.use_as_origin).setOnClickListener { + (activity as MainActivity).viewModel.setOrigin(Place(stop, stop.location().latitude, stop.location().longitude)) + (ctx as MainActivity).showBadge() + dismiss() + } + + content.findViewById<Button>(R.id.use_as_destination).setOnClickListener { + (activity as MainActivity).viewModel.setDestination(Place(stop, stop.location().latitude, stop.location().longitude)) + (ctx as MainActivity).showBadge() + dismiss() } stop.changeOptions(ctx, Stop.LineDecoration.NONE).let { changeOptions -> 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 90e2b6064503ef5cddf50cbcbd9be446256e856f..dd03572d986e0f0f4a47dcb6da195fa45bb46ee0 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 @@ -29,16 +29,32 @@ val steps: List ?,*/ ) @Parcelize -class Place(val stop: Stop, val latitude: Double, val longitude: Double): Parcelable { +class Place(val stop: Stop, val latitude: Double, val longitude: Double) : Parcelable { constructor(place: Place) : this( stop = Stop(place), latitude = place.lat.toDouble(), longitude = place.lon.toDouble() + ) + + constructor(latitude: Double, longitude: Double) : this( + Stop( + "", "", + "", "", null, Position(latitude, longitude), emptyList<ChangeOption>(), null + ), latitude, longitude ) fun planString(): String = if (stop.code == "") { "${latitude},${longitude},0" } else { stop.code + } + + fun shortString(): String = if (stop.name == "") { + "%.2f, %.2f".format( + latitude, + longitude + ) + } else { + stop.name } } diff --git a/app/src/main/res/drawable/pin.xml b/app/src/main/res/drawable/pin.xml new file mode 100644 index 0000000000000000000000000000000000000000..372ded410e5b785c0af89e45edf50e9457981691 --- /dev/null +++ b/app/src/main/res/drawable/pin.xml @@ -0,0 +1,17 @@ +<!-- +SPDX-FileCopyrightText: Google + +SPDX-License-Identifier: Apache-2.0 +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,2L12,2C8.13,2 5,5.13 5,9c0,1.74 0.5,3.37 1.41,4.84c0.95,1.54 2.2,2.86 3.16,4.4c0.47,0.75 0.81,1.45 1.17,2.26C11,21.05 11.21,22 12,22h0c0.79,0 1,-0.95 1.25,-1.5c0.37,-0.81 0.7,-1.51 1.17,-2.26c0.96,-1.53 2.21,-2.85 3.16,-4.4C18.5,12.37 19,10.74 19,9C19,5.13 15.87,2 12,2zM12,11.75c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5S13.38,11.75 12,11.75z" + android:fillColor="?attr/colorOnSurface"/> + <path + android:pathData="M12,3.361L12,3.361C8.67,3.361 5.977,6.054 5.977,9.384c0,1.497 0.43,2.9 1.213,4.164 0.817,1.325 1.893,2.461 2.719,3.786 0.404,0.645 0.697,1.248 1.007,1.945C11.14,19.752 11.32,20.569 12,20.569l0,0c0.68,0 0.86,-0.817 1.076,-1.291 0.318,-0.697 0.602,-1.299 1.007,-1.945 0.826,-1.316 1.902,-2.452 2.719,-3.786C17.593,12.283 18.023,10.881 18.023,9.384 18.023,6.054 15.33,3.361 12,3.361ZM12,11.75c-1.187,0 -2.151,-0.964 -2.151,-2.151 0,-1.187 0.964,-2.151 2.151,-2.151 1.187,0 2.151,0.964 2.151,2.151 0,1.187 -0.964,2.151 -2.151,2.151z" + android:fillColor="?attr/colorPrimary"/> +</vector> diff --git a/app/src/main/res/drawable/send.xml b/app/src/main/res/drawable/send.xml index 42caa11f3d69d9e70a3b73d5f03d617c349af2cc..db190a641930863725e6135f37f7a89a37fec9c5 100644 --- a/app/src/main/res/drawable/send.xml +++ b/app/src/main/res/drawable/send.xml @@ -8,7 +8,7 @@ android:width="24dp" android:height="24dp" android:autoMirrored="true" - android:tint="#000000" + android:tint="?attr/colorOnSurface" android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/layout/fragment_journey.xml b/app/src/main/res/layout/fragment_journey.xml index 74e1360a0b536a83402693f56156f6823dbbd843..6e5d145b67459d8320e9e653aefb401be76bd24f 100644 --- a/app/src/main/res/layout/fragment_journey.xml +++ b/app/src/main/res/layout/fragment_journey.xml @@ -7,346 +7,376 @@ SPDX-License-Identifier: GPL-3.0-or-later --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tool="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tool:context="xyz.apiote.bimba.czwek.dashboard.ui.journey.JourneyFragment"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tool="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tool:context="xyz.apiote.bimba.czwek.dashboard.ui.journey.JourneyFragment"> - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/origin_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="16dp" - android:layout_marginTop="16dp" - android:layout_marginRight="16dp" - android:hint="@string/origin_input_hint" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:startIconDrawable="@drawable/origin"> + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/origin_input" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginTop="16dp" + android:layout_marginRight="16dp" + android:hint="@string/origin_input_hint" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:startIconDrawable="@drawable/origin"> - <com.google.android.material.textfield.TextInputEditText - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:imeOptions="actionSearch" /> - </com.google.android.material.textfield.TextInputLayout> + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/origin" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionSearch" /> + </com.google.android.material.textfield.TextInputLayout> - <com.google.android.material.chip.ChipGroup - android:id="@+id/origin_chips" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - app:layout_constraintEnd_toEndOf="@+id/origin_input" - app:layout_constraintStart_toStartOf="@+id/origin_input" - app:layout_constraintTop_toBottomOf="@+id/origin_input" - tool:layout_height="48dp" /> + <com.google.android.material.chip.ChipGroup + android:id="@+id/origin_suggestions" + android:layout_marginLeft="16dp" + android:layout_marginTop="4dp" + android:layout_marginRight="16dp" + android:layout_width="match_parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/origin_input" + android:layout_height="wrap_content"> - <!-- via - required: false - List of via stops to visit (only stop IDs, no coordinates allowed for now). - maxItems: 2 + <com.google.android.material.chip.Chip + android:id="@+id/origin_chip_here" + style="@style/Widget.Material3.Chip.Suggestion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:chipIcon="@drawable/gps_black" + app:chipIconTint="?attr/colorOnSurface" + app:chipIconVisible="true" + android:checkable="false" + android:text="@string/here" /> - viaMinimumStay - required: false - Optional. If not set, the default is `0,0` - no stay required. - For each `via` stop a minimum stay duration in minutes. + </com.google.android.material.chip.ChipGroup> - The value `0` signals that it's allowed to stay in the same trip. - This enables via stays without counting a transfer and can lead - to better connections with less transfers. Transfer connections can - still be found with `viaMinimumStay=0`. - default: [ 0, 0 ] - type: array - maxItems: 2 - --> + <!-- via + required: false + List of via stops to visit (only stop IDs, no coordinates allowed for now). + maxItems: 2 - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/destination_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="16dp" - android:layout_marginTop="16dp" - android:layout_marginRight="16dp" - android:hint="@string/destination_input_hint" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/origin_chips" - app:startIconDrawable="@drawable/destination"> + viaMinimumStay + required: false + Optional. If not set, the default is `0,0` - no stay required. + For each `via` stop a minimum stay duration in minutes. - <com.google.android.material.textfield.TextInputEditText - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:imeOptions="actionSearch" /> - </com.google.android.material.textfield.TextInputLayout> + The value `0` signals that it's allowed to stay in the same trip. + This enables via stays without counting a transfer and can lead + to better connections with less transfers. Transfer connections can + still be found with `viaMinimumStay=0`. + default: [ 0, 0 ] + type: array + maxItems: 2 + --> - <com.google.android.material.chip.ChipGroup - android:id="@+id/destination_chips" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - app:layout_constraintEnd_toEndOf="@+id/destination_input" - app:layout_constraintStart_toStartOf="@+id/destination_input" - app:layout_constraintTop_toBottomOf="@+id/destination_input" - tool:layout_height="48dp" /> + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/destination_input" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginTop="16dp" + android:layout_marginRight="16dp" + android:hint="@string/destination_input_hint" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/origin_suggestions" + app:startIconDrawable="@drawable/destination"> - <com.google.android.material.divider.MaterialDivider - android:id="@+id/materialDivider" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - app:dividerInsetEnd="16dp" - app:dividerInsetStart="16dp" - app:layout_constraintTop_toBottomOf="@id/destination_chips" /> + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/destination" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:imeOptions="actionSearch" /> + </com.google.android.material.textfield.TextInputLayout> - <com.google.android.material.chip.ChipGroup - android:id="@+id/chips_params_time" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/materialDivider"> + <com.google.android.material.chip.ChipGroup + android:id="@+id/destination_suggestions" + android:layout_marginLeft="16dp" + android:layout_marginTop="4dp" + android:layout_marginRight="16dp" + android:layout_width="match_parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/destination_input" + android:layout_height="wrap_content"> - <!-- 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:text="@string/depart_after" - app:checkedIconEnabled="false" - app:closeIcon="@drawable/dropdown" - tool:ignore="MissingConstraints" - app:closeIconEnabled="true" /> + <com.google.android.material.chip.Chip + android:id="@+id/destination_chip_here" + style="@style/Widget.Material3.Chip.Suggestion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:chipIcon="@drawable/gps_black" + app:chipIconTint="?attr/colorOnSurface" + app:chipIconVisible="true" + android:checkable="false" + android:text="@string/here" /> - <com.google.android.material.chip.Chip - android:id="@+id/chip_date" - style="@style/Widget.Material3.Chip.Filter" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/today" - app:checkedIconEnabled="false" - app:closeIcon="@drawable/dropdown" - app:closeIconEnabled="true" - tool:ignore="MissingConstraints" /> + </com.google.android.material.chip.ChipGroup> - <com.google.android.material.chip.Chip - android:id="@+id/chip_time" - style="@style/Widget.Material3.Chip.Filter" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/now" - app:checkedIconEnabled="false" - app:closeIcon="@drawable/dropdown" - tool:ignore="MissingConstraints" - app:closeIconEnabled="true" /> + <com.google.android.material.divider.MaterialDivider + android:id="@+id/materialDivider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:dividerInsetEnd="16dp" + app:dividerInsetStart="16dp" + app:layout_constraintTop_toBottomOf="@id/destination_suggestions" /> - </com.google.android.material.chip.ChipGroup> + <com.google.android.material.chip.ChipGroup + android:id="@+id/chips_params_time" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/materialDivider"> - <com.google.android.material.chip.ChipGroup - android:id="@+id/chips_params_accessible" - android:layout_width="match_parent" - android:layout_marginTop="8dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/chips_params_time" - android:layout_height="wrap_content"> + <!-- 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:text="@string/depart_after" + app:checkedIconEnabled="false" + app:closeIcon="@drawable/dropdown" + app:closeIconEnabled="true" + tool:ignore="MissingConstraints" /> - <com.google.android.material.chip.Chip - android:id="@+id/chip_wheelchair" - style="@style/Widget.Material3.Chip.Filter" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - app:chipIcon="@drawable/wheelchair" - app:chipIconEnabled="true" - android:text="@string/wheelchair_accessible" - tool:ignore="MissingConstraints" /> + <com.google.android.material.chip.Chip + android:id="@+id/chip_date" + style="@style/Widget.Material3.Chip.Filter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/today" + app:checkedIconEnabled="false" + app:closeIcon="@drawable/dropdown" + app:closeIconEnabled="true" + tool:ignore="MissingConstraints" /> - <com.google.android.material.chip.Chip - android:id="@+id/chip_bike" - style="@style/Widget.Material3.Chip.Filter" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - app:chipIcon="@drawable/bike" - app:chipIconEnabled="true" - android:text="@string/bike_transport" - tool:ignore="MissingConstraints" /> + <com.google.android.material.chip.Chip + android:id="@+id/chip_time" + style="@style/Widget.Material3.Chip.Filter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/now" + app:checkedIconEnabled="false" + app:closeIcon="@drawable/dropdown" + app:closeIconEnabled="true" + tool:ignore="MissingConstraints" /> - </com.google.android.material.chip.ChipGroup> + </com.google.android.material.chip.ChipGroup> - <Button - android:id="@+id/elevatedButton" - style="@style/Widget.Material3.Button.Icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - app:icon="@drawable/journey_outline" - android:text="@string/go" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + <com.google.android.material.chip.ChipGroup + android:id="@+id/chips_params_accessible" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/chips_params_time"> - <!-- - - name: maxTransfers - required: false - The maximum number of allowed transfers. - If not provided, the routing uses the server-side default value - which is hardcoded and very high to cover all use cases. - schema: - type: integer + <com.google.android.material.chip.Chip + android:id="@+id/chip_wheelchair" + style="@style/Widget.Material3.Chip.Filter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/wheelchair_accessible" + app:chipIcon="@drawable/wheelchair" + app:chipIconEnabled="true" + tool:ignore="MissingConstraints" /> - - name: maxHours - required: false - The maximum travel time in hours. - If not provided, the routing to uses the value - hardcoded in the server which is usually quite high. - schema: - type: number + <com.google.android.material.chip.Chip + android:id="@+id/chip_bike" + style="@style/Widget.Material3.Chip.Filter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bike_transport" + app:chipIcon="@drawable/bike" + app:chipIconEnabled="true" + tool:ignore="MissingConstraints" /> - - name: minTransferTime - required: false - Minimum transfer time for each transfer in minutes. - schema: - type: integer - default: 0 + </com.google.android.material.chip.ChipGroup> - - name: additionalTransferTime - required: false - Additional transfer time reserved for each transfer in minutes. - schema: - type: integer - default: 0 + <Button + android:id="@+id/goButton" + style="@style/Widget.Material3.Button.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/go" + app:icon="@drawable/journey_outline" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> - - name: transferTimeFactor - required: false - Factor to multiply minimum required transfer times with. - Values smaller than 1.0 are not supported. - schema: - type: number - default: 1.0 + <!-- + - name: maxTransfers + required: false + The maximum number of allowed transfers. + If not provided, the routing uses the server-side default value + which is hardcoded and very high to cover all use cases. + schema: + type: integer - - name: maxMatchingDistance - required: true - Maximum matching distance in meters to match geo coordinates to the street network. - schema: - type: number - default: 25 + - name: maxHours + required: false + The maximum travel time in hours. + If not provided, the routing to uses the value + hardcoded in the server which is usually quite high. + schema: + type: number - - name: transitModes - required: false - Optional. Default is `TRANSIT` which allows all transit modes (no restriction). - Allowed modes for the transit part. If empty, no transit connections will be computed. - For example, this can be used to allow only `METRO,SUBWAY,TRAM`. - schema: - default: - - TRANSIT - type: array + - name: minTransferTime + required: false + Minimum transfer time for each transfer in minutes. + schema: + type: integer + default: 0 - - name: directModes - required: false - Optional. Default is `WALK` which will compute walking routes as direct connections. - Modes used for direction connections from start to destination without using transit. - Results will be returned on the `direct` key. - Note: Direct connections will only be returned on the first call. For paging calls, they can be omitted. - Note: Transit connections that are slower than the fastest direct connection will not show up. - This is being used as a cut-off during transit routing to speed up the search. - To prevent this, it's possible to send two separate requests (one with only `transitModes` and one with only `directModes`). - Only non-transit modes such as `WALK`, `BIKE`, `CAR`, `BIKE_SHARING`, etc. can be used. - schema: - default: - - WALK - type: array + - name: additionalTransferTime + required: false + Additional transfer time reserved for each transfer in minutes. + schema: + type: integer + default: 0 + + - name: transferTimeFactor + required: false + Factor to multiply minimum required transfer times with. + Values smaller than 1.0 are not supported. + schema: + type: number + default: 1.0 + + - name: maxMatchingDistance + required: true + Maximum matching distance in meters to match geo coordinates to the street network. + schema: + type: number + default: 25 + + - name: transitModes + required: false + Optional. Default is `TRANSIT` which allows all transit modes (no restriction). + Allowed modes for the transit part. If empty, no transit connections will be computed. + For example, this can be used to allow only `METRO,SUBWAY,TRAM`. + schema: + default: + - TRANSIT + type: array + + - name: directModes + required: false + Optional. Default is `WALK` which will compute walking routes as direct connections. + Modes used for direction connections from start to destination without using transit. + Results will be returned on the `direct` key. + Note: Direct connections will only be returned on the first call. For paging calls, they can be omitted. + Note: Transit connections that are slower than the fastest direct connection will not show up. + This is being used as a cut-off during transit routing to speed up the search. + To prevent this, it's possible to send two separate requests (one with only `transitModes` and one with only `directModes`). + Only non-transit modes such as `WALK`, `BIKE`, `CAR`, `BIKE_SHARING`, etc. can be used. + schema: + default: + - WALK + type: array - - name: preTransitModes - required: false - Optional. Default is `WALK`. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). - A list of modes that are allowed to be used from the `from` coordinate to the first transit stop. Example: `WALK,BIKE_SHARING`. - schema: - default: - - WALK - type: array + - name: preTransitModes + required: false + Optional. Default is `WALK`. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + A list of modes that are allowed to be used from the `from` coordinate to the first transit stop. Example: `WALK,BIKE_SHARING`. + schema: + default: + - WALK + type: array - - name: postTransitModes - required: false - Optional. Default is `WALK`. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). - A list of modes that are allowed to be used from the last transit stop to the `to` coordinate. Example: `WALK,BIKE_SHARING`. - schema: - default: - - WALK - type: array + - name: postTransitModes + required: false + Optional. Default is `WALK`. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + A list of modes that are allowed to be used from the last transit stop to the `to` coordinate. Example: `WALK,BIKE_SHARING`. + schema: + default: + - WALK + type: array - - name: numItineraries - required: false - The minimum number of itineraries to compute. - This is only relevant if `timetableView=true`. - schema: - type: integer - default: 5 + - name: numItineraries + required: false + The minimum number of itineraries to compute. + This is only relevant if `timetableView=true`. + schema: + type: integer + default: 5 - - name: pageCursor - required: false - Use the cursor to go to the next "page" of itineraries. - Copy the cursor from the last response and keep the original request as is. - This will enable you to search for itineraries in the next or previous time-window. + - name: pageCursor + required: false + Use the cursor to go to the next "page" of itineraries. + Copy the cursor from the last response and keep the original request as is. + This will enable you to search for itineraries in the next or previous time-window. - - name: timetableView - required: false - Search for the best trip options within a time window. - If true two itineraries are considered optimal - if one is better on arrival time (earliest wins) - and the other is better on departure time (latest wins). - In combination with arriveBy this parameter cover the following use cases: - `timetable=false` = waiting for the first transit departure/arrival is considered travel time: - - `arriveBy=true`: event (e.g. a meeting) starts at 10:00 am, - compute the best journeys that arrive by that time (maximizes departure time) - - `arriveBy=false`: event (e.g. a meeting) ends at 11:00 am, - compute the best journeys that depart after that time - `timetable=true` = optimize "later departure" + "earlier arrival" and give all options over a time window: - - `arriveBy=true`: the time window around `date` and `time` refers to the arrival time window - - `arriveBy=false`: the time window around `date` and `time` refers to the departure time window - schema: - type: boolean - default: true + - name: timetableView + required: false + Search for the best trip options within a time window. + If true two itineraries are considered optimal + if one is better on arrival time (earliest wins) + and the other is better on departure time (latest wins). + In combination with arriveBy this parameter cover the following use cases: + `timetable=false` = waiting for the first transit departure/arrival is considered travel time: + - `arriveBy=true`: event (e.g. a meeting) starts at 10:00 am, + compute the best journeys that arrive by that time (maximizes departure time) + - `arriveBy=false`: event (e.g. a meeting) ends at 11:00 am, + compute the best journeys that depart after that time + `timetable=true` = optimize "later departure" + "earlier arrival" and give all options over a time window: + - `arriveBy=true`: the time window around `date` and `time` refers to the arrival time window + - `arriveBy=false`: the time window around `date` and `time` refers to the departure time window + schema: + type: boolean + default: true - - name: searchWindow - required: false - Optional. Default is 2 hours which is `7200`. - The length of the search-window in seconds. Default value two hours. - - `arriveBy=true`: number of seconds between the earliest departure time and latest departure time - - `arriveBy=false`: number of seconds between the earliest arrival time and the latest arrival time - schema: - type: integer - default: 7200 - minium: 0 + - name: searchWindow + required: false + Optional. Default is 2 hours which is `7200`. + The length of the search-window in seconds. Default value two hours. + - `arriveBy=true`: number of seconds between the earliest departure time and latest departure time + - `arriveBy=false`: number of seconds between the earliest arrival time and the latest arrival time + schema: + type: integer + default: 7200 + minium: 0 - - name: maxPreTransitTime - required: false - Maximum time in seconds for the first street leg. - schema: - type: integer - default: 900 - minimum: 0 + - name: maxPreTransitTime + required: false + Maximum time in seconds for the first street leg. + schema: + type: integer + default: 900 + minimum: 0 - - name: maxPostTransitTime - required: false - Maximum time in seconds for the last street leg. - schema: - type: integer - default: 900 - minimum: 0 + - name: maxPostTransitTime + required: false + Maximum time in seconds for the last street leg. + schema: + type: integer + default: 900 + minimum: 0 - - name: maxDirectTime - required: false - Maximum time in seconds for direct connections. - schema: - type: integer - default: 1800 - minimum: 0 - --> + - name: maxDirectTime + required: false + Maximum time in seconds for direct connections. + schema: + type: integer + default: 1800 + minimum: 0 + --> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/place_bottom_sheet.xml b/app/src/main/res/layout/place_bottom_sheet.xml new file mode 100644 index 0000000000000000000000000000000000000000..8f32c1a27fd4d8f57a94510b0167f3ea8af24c4f --- /dev/null +++ b/app/src/main/res/layout/place_bottom_sheet.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- +SPDX-FileCopyrightText: Adam Evyčędo + +SPDX-License-Identifier: GPL-3.0-or-later +--> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:paddingBottom="16dp" + android:layout_height="match_parent" + xmlns:tool="http://schemas.android.com/tools"> + + <com.google.android.material.bottomsheet.BottomSheetDragHandleView + android:id="@+id/drag_handle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/coordinates" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/drag_handle" + tool:text="17.151864867, 52.215648641" /> + + <androidx.constraintlayout.helper.widget.Flow + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:constraint_referenced_ids="use_as_origin,use_as_destination" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/coordinates" /> + + <Button + android:id="@+id/use_as_origin" + style="@style/Widget.Material3.Button.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/use_as_origin" + app:icon="@drawable/origin" /> + + <Button + android:id="@+id/use_as_destination" + style="@style/Widget.Material3.Button.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/use_as_destination" + app:icon="@drawable/destination" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/stop_bottom_sheet.xml b/app/src/main/res/layout/stop_bottom_sheet.xml index 8b7419d9a9dfa5803e59a1c207e287a826656b2b..75a659e26de9b39f20a20c47da6839484a749294 100644 --- a/app/src/main/res/layout/stop_bottom_sheet.xml +++ b/app/src/main/res/layout/stop_bottom_sheet.xml @@ -66,4 +66,29 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/departures_button" /> + <androidx.constraintlayout.helper.widget.Flow + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:constraint_referenced_ids="use_as_origin,use_as_destination" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/navigation_button" /> + + <Button + android:id="@+id/use_as_origin" + style="@style/Widget.Material3.Button.TextButton.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/use_as_origin" + app:icon="@drawable/origin" /> + + <Button + android:id="@+id/use_as_destination" + style="@style/Widget.Material3.Button.TextButton.Icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/use_as_destination" + app:icon="@drawable/destination" /> + </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f796af4d2f8128f2c97dedefdb33ccc04e53508..6e8c3ac7e24ad1cb12e0698b034c734f6987bbbc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -309,4 +309,7 @@ <item quantity="one">%1$d stop</item> <item quantity="other">%1$d stops</item> </plurals> + <string name="use_as_origin">use as origin</string> + <string name="use_as_destination">use as destination</string> + <string name="here">here</string> </resources> diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0480a5fed0a434e65ffaebf0d90a926db77025fe..3826ee91e7f57a78ad8f59137d82960edabe3322 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -1,5 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> -<resources> +<!-- +SPDX-FileCopyrightText: Adam Evyčędo and contributors using Weblate + +SPDX-License-Identifier: GPL-3.0-or-later +--><resources> <string name="error_403">Sinu sisestatud ligipääsutunnus pole õige</string> <string name="error_404">Otsitavat ei leidu</string> <string name="error_50x">Serveris tekkis viga. Palun proovi hiljem uuesti</string> diff --git a/app/src/main/res/xml/journey_chip.xml b/app/src/main/res/xml/journey_chip.xml new file mode 100644 index 0000000000000000000000000000000000000000..0efc50369675e1c049426633e68f729fc77527cf --- /dev/null +++ b/app/src/main/res/xml/journey_chip.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +SPDX-FileCopyrightText: Adam Evyčędo + +SPDX-License-Identifier: GPL-3.0-or-later +--> +<chip xmlns:android="http://schemas.android.com/apk/res/android" + android:text="-180.00, -90.00" /> \ No newline at end of file