checkpoint
This commit is contained in:
		
							parent
							
								
									b0edefcb4b
								
							
						
					
					
						commit
						0133524fa2
					
				
					 7 changed files with 226 additions and 45 deletions
				
			
		|  | @ -30,8 +30,8 @@ import java.util.* | |||
| data class Track ( | ||||
|     val database: Database, | ||||
|     val device_id: String, | ||||
|     val start_time: Date, | ||||
|     val stop_time: Date, | ||||
|     var start_time: Date, | ||||
|     var end_time: Date, | ||||
|     var name: String = "", | ||||
|     val trkpts: ArrayDeque<Trkpt> = ArrayDeque<Trkpt>(), | ||||
|     var view_latitude: Double = Keys.DEFAULT_LATITUDE, | ||||
|  | @ -135,7 +135,6 @@ data class Track ( | |||
|                 continue | ||||
|             } | ||||
|             stats.distance += previous.toLocation().distanceTo(trkpt.toLocation()) | ||||
|             Log.i("VOUSSOIR", previous.toLocation().distanceTo(trkpt.toLocation()).toString()) | ||||
|             val ascentdiff = trkpt.altitude - previous.altitude | ||||
|             if (ascentdiff > 0) | ||||
|             { | ||||
|  | @ -169,8 +168,9 @@ data class Track ( | |||
|     { | ||||
|         val cursor: Cursor = database.connection.rawQuery( | ||||
|             "SELECT lat, lon, time, ele, accuracy, sat FROM trkpt WHERE device_id = ? AND time > ? AND time < ? ORDER BY time ASC", | ||||
|             arrayOf(device_id, start_time.time.toString(), stop_time.time.toString()) | ||||
|             arrayOf(device_id, start_time.time.toString(), end_time.time.toString()) | ||||
|         ) | ||||
|         Log.i("VOUSSOIR", "Querying points between ${start_time} -- ${end_time}") | ||||
|         val COLUMN_LAT = cursor.getColumnIndex("lat") | ||||
|         val COLUMN_LON = cursor.getColumnIndex("lon") | ||||
|         val COLUMN_ELE = cursor.getColumnIndex("ele") | ||||
|  |  | |||
|  | @ -49,9 +49,8 @@ class TrackFragment : Fragment(), YesNoDialog.YesNoDialogListener | |||
|             name=this.requireArguments().getString(Keys.ARG_TRACK_TITLE, ""), | ||||
|             device_id= this.requireArguments().getString(Keys.ARG_TRACK_DEVICE_ID, ""), | ||||
|             start_time= iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_START_TIME)!!), | ||||
|             stop_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!), | ||||
|             end_time=iso8601_format.parse(this.requireArguments().getString(Keys.ARG_TRACK_STOP_TIME)!!), | ||||
|         ) | ||||
|         track.load_trkpts() | ||||
|         layout = TrackFragmentLayoutHolder(activity as Context, inflater, container, track) | ||||
| 
 | ||||
|         // set up share button | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ class TracklistFragment : Fragment(), TracklistAdapter.TracklistAdapterListener, | |||
|             Keys.ARG_TRACK_TITLE to track.name, | ||||
|             Keys.ARG_TRACK_DEVICE_ID to track.device_id, | ||||
|             Keys.ARG_TRACK_START_TIME to iso8601_format.format(track.start_time), | ||||
|             Keys.ARG_TRACK_STOP_TIME to iso8601_format.format(track.stop_time), | ||||
|             Keys.ARG_TRACK_STOP_TIME to iso8601_format.format(track.end_time), | ||||
|         ) | ||||
|         findNavController().navigate(R.id.fragment_track, bundle) | ||||
|     } | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle | |||
|                 Log.i("VOUSSOIR", "TracklistAdapter prep track ${trackdate}") | ||||
|                 if (start_time != null && stop_time != null) | ||||
|                 { | ||||
|                     val track = Track(database=database, device_id=device_id, start_time=start_time, stop_time=stop_time) | ||||
|                     val track = Track(database=database, device_id=device_id, start_time=start_time, end_time=stop_time) | ||||
|                     track.name = "$trackdate $device_id" | ||||
|                     tracks.add(track) | ||||
|                 } | ||||
|  |  | |||
|  | @ -18,10 +18,15 @@ package org.y20k.trackbook.ui | |||
| 
 | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import android.util.Log | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.DatePicker | ||||
| import android.widget.ImageButton | ||||
| import android.widget.TimePicker | ||||
| import android.widget.Toast | ||||
| import androidx.constraintlayout.widget.Group | ||||
| import androidx.core.widget.NestedScrollView | ||||
|  | @ -34,19 +39,17 @@ import org.osmdroid.events.ZoomEvent | |||
| import org.osmdroid.tileprovider.tilesource.TileSourceFactory | ||||
| import org.osmdroid.util.GeoPoint | ||||
| import org.osmdroid.views.MapView | ||||
| import org.osmdroid.views.overlay.ItemizedIconOverlay | ||||
| import org.osmdroid.views.overlay.OverlayItem | ||||
| import org.osmdroid.views.overlay.TilesOverlay | ||||
| import org.osmdroid.views.overlay.compass.CompassOverlay | ||||
| import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider | ||||
| import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay | ||||
| import org.y20k.trackbook.Keys | ||||
| import org.y20k.trackbook.R | ||||
| import org.y20k.trackbook.Track | ||||
| import org.y20k.trackbook.TrackStatistics | ||||
| import org.y20k.trackbook.helpers.* | ||||
| import java.util.* | ||||
| 
 | ||||
| /* | ||||
|  * TrackFragmentLayoutHolder class | ||||
|  */ | ||||
| //data class TrackFragmentLayoutHolder(private var context: Context, private var markerListener: MapOverlayHelper.MarkerListener, private var inflater: LayoutInflater, private var statusBarHeight: Int, private var container: ViewGroup?, var track: Track): MapListener { TODO REMOVE | ||||
| data class TrackFragmentLayoutHolder( | ||||
|     private var context: Context, | ||||
|     private var inflater: LayoutInflater, | ||||
|  | @ -54,11 +57,16 @@ data class TrackFragmentLayoutHolder( | |||
|     var track: Track | ||||
| ): MapListener | ||||
| { | ||||
|     /* Main class variables */ | ||||
|     val rootView: View | ||||
|     val save_track_button: ImageButton | ||||
|     val deleteButton: ImageButton | ||||
|     val trackNameView: MaterialTextView | ||||
|     val track_query_start_date: DatePicker | ||||
|     val track_query_start_time: TimePicker | ||||
|     val track_query_end_date: DatePicker | ||||
|     val track_query_end_time: TimePicker | ||||
|     var track_query_start_time_previous: Int | ||||
|     var track_query_end_time_previous: Int | ||||
|     private val mapView: MapView | ||||
|     private var controller: IMapController | ||||
|     private val statisticsSheetBehavior: BottomSheetBehavior<View> | ||||
|  | @ -78,17 +86,93 @@ data class TrackFragmentLayoutHolder( | |||
|     private val negativeElevationView: MaterialTextView | ||||
|     private val elevationDataViews: Group | ||||
|     private val useImperialUnits: Boolean | ||||
|     private val handler: Handler = Handler(Looper.getMainLooper()) | ||||
|     private var special_points_overlay: ItemizedIconOverlay<OverlayItem>? = null | ||||
|     private var track_overlay: SimpleFastPointOverlay? = null | ||||
|     val RERENDER_DELAY: Long = 1000 | ||||
| 
 | ||||
|     init | ||||
|     { | ||||
|         // find views | ||||
|         rootView = inflater.inflate(R.layout.fragment_track, container, false) | ||||
|         mapView = rootView.findViewById(R.id.map) | ||||
|         save_track_button = rootView.findViewById(R.id.save_button) | ||||
|         deleteButton = rootView.findViewById(R.id.delete_button) | ||||
|         trackNameView = rootView.findViewById(R.id.statistics_track_name_headline) | ||||
| 
 | ||||
|         // basic map setup | ||||
|         track.load_trkpts() | ||||
|         val actual_start_time: Date = if (track.trkpts.isEmpty()) track.start_time else Date(track.trkpts.first().time) | ||||
|         val actual_end_time: Date = if (track.trkpts.isEmpty()) track.end_time else Date(track.trkpts.last().time) | ||||
| 
 | ||||
|         track_query_start_date = rootView.findViewById(R.id.track_query_start_date) | ||||
|         val start_cal = GregorianCalendar() | ||||
|         start_cal.time = actual_start_time | ||||
|         track_query_start_date.init(start_cal.get(Calendar.YEAR), start_cal.get(Calendar.MONTH), start_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener { | ||||
|             override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int) | ||||
|             { | ||||
|                 handler.removeCallbacks(requery_and_render) | ||||
|                 handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         track_query_start_time = rootView.findViewById(R.id.track_query_start_time) | ||||
|         track_query_start_time.setIs24HourView(true) | ||||
|         track_query_start_time.hour = actual_start_time.hours | ||||
|         track_query_start_time.minute = actual_start_time.minutes | ||||
|         track_query_start_time_previous = (actual_start_time.hours * 60) + actual_start_time.minutes | ||||
|         track_query_start_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{ | ||||
|             override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int) | ||||
|             { | ||||
|                 handler.removeCallbacks(requery_and_render) | ||||
|                 val newminute = (p1 * 60) + p2 | ||||
|                 Log.i("VOUSSOIR", "End time changed $newminute") | ||||
|                 if (newminute < track_query_start_time_previous && (track_query_start_time_previous - newminute > 60)) | ||||
|                 { | ||||
|                     increment_datepicker(track_query_start_date) | ||||
|                 } | ||||
|                 else if (newminute > track_query_start_time_previous && (newminute - track_query_start_time_previous > 60)) | ||||
|                 { | ||||
|                     decrement_datepicker(track_query_start_date) | ||||
|                 } | ||||
|                 track_query_start_time_previous = newminute | ||||
|                 handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         track_query_end_date = rootView.findViewById(R.id.track_query_end_date) | ||||
|         val end_cal = GregorianCalendar() | ||||
|         end_cal.time = actual_end_time | ||||
|         track_query_end_date.init(end_cal.get(Calendar.YEAR), end_cal.get(Calendar.MONTH), end_cal.get(Calendar.DAY_OF_MONTH), object: DatePicker.OnDateChangedListener { | ||||
|             override fun onDateChanged(p0: DatePicker?, p1: Int, p2: Int, p3: Int) | ||||
|             { | ||||
|                 handler.removeCallbacks(requery_and_render) | ||||
|                 handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         track_query_end_time = rootView.findViewById(R.id.track_query_end_time) | ||||
|         track_query_end_time.setIs24HourView(true) | ||||
|         track_query_end_time.hour = actual_end_time.hours | ||||
|         track_query_end_time.minute = actual_end_time.minutes | ||||
|         track_query_end_time_previous = (actual_end_time.hours * 60) + actual_end_time.minutes | ||||
|         track_query_end_time.setOnTimeChangedListener(object : TimePicker.OnTimeChangedListener{ | ||||
|             override fun onTimeChanged(p0: TimePicker?, p1: Int, p2: Int) | ||||
|             { | ||||
|                 handler.removeCallbacks(requery_and_render) | ||||
|                 val newminute = (p1 * 60) + p2 | ||||
|                 Log.i("VOUSSOIR", "End time changed $newminute") | ||||
|                 if (newminute < track_query_end_time_previous && (track_query_end_time_previous - newminute > 60)) | ||||
|                 { | ||||
|                     increment_datepicker(track_query_end_date) | ||||
|                 } | ||||
|                 else if (newminute > track_query_end_time_previous && (newminute - track_query_end_time_previous > 60)) | ||||
|                 { | ||||
|                     decrement_datepicker(track_query_end_date) | ||||
|                 } | ||||
|                 track_query_end_time_previous = newminute | ||||
|                 handler.postDelayed(requery_and_render, RERENDER_DELAY) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         controller = mapView.controller | ||||
|         mapView.addMapListener(this) | ||||
|         mapView.isTilesScaledToDpi = true | ||||
|  | @ -99,7 +183,6 @@ data class TrackFragmentLayoutHolder( | |||
|         controller.setCenter(GeoPoint(track.view_latitude, track.view_longitude)) | ||||
|         controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL) | ||||
| 
 | ||||
|         // get views for statistics sheet | ||||
|         statisticsSheet = rootView.findViewById(R.id.statistics_sheet) | ||||
|         statisticsView = rootView.findViewById(R.id.statistics_view) | ||||
|         distanceView = rootView.findViewById(R.id.statistics_data_distance) | ||||
|  | @ -116,36 +199,59 @@ data class TrackFragmentLayoutHolder( | |||
|         negativeElevationView = rootView.findViewById(R.id.statistics_data_negative_elevation) | ||||
|         elevationDataViews = rootView.findViewById(R.id.elevation_data) | ||||
| 
 | ||||
|         // get measurement unit system | ||||
|         useImperialUnits = PreferencesHelper.loadUseImperialUnits() | ||||
| 
 | ||||
|         // set dark map tiles, if necessary | ||||
|         if (AppThemeHelper.isDarkModeOn(context as Activity)) { | ||||
|             mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS) | ||||
|         } | ||||
| 
 | ||||
|         // add compass to map | ||||
|         val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView) | ||||
|         compassOverlay.enableCompass() | ||||
|         compassOverlay.setCompassCenter(36f, 36f) | ||||
| //        compassOverlay.setCompassCenter(36f, 36f + (statusBarHeight / UiHelper.getDensityScalingFactor(context))) TODO REMOVE | ||||
|         mapView.overlays.add(compassOverlay) | ||||
| 
 | ||||
|         if (track.trkpts.isNotEmpty()) { | ||||
|             createSpecialMakersTrackOverlay(context, mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true) | ||||
|             createTrackOverlay(context, mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED) | ||||
|         } | ||||
| 
 | ||||
|         // set up and show statistics sheet | ||||
|         statisticsSheetBehavior = BottomSheetBehavior.from<View>(statisticsSheet) | ||||
|         statisticsSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED | ||||
| 
 | ||||
|         render_track() | ||||
|     } | ||||
| 
 | ||||
|     fun render_track() | ||||
|     { | ||||
|         if (special_points_overlay != null) | ||||
|         { | ||||
|             mapView.overlays.remove(special_points_overlay) | ||||
|             mapView.overlays.remove(track_overlay) | ||||
|         } | ||||
|         if (track.trkpts.isNotEmpty()) | ||||
|         { | ||||
|             special_points_overlay = createSpecialMakersTrackOverlay(context, mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED, displayStartEndMarker = true) | ||||
|             track_overlay = createTrackOverlay(context, mapView, track.trkpts, Keys.STATE_TRACKING_STOPPED) | ||||
|         } | ||||
|         setupStatisticsViews() | ||||
|     } | ||||
| 
 | ||||
|     /* Sets up the statistics sheet */ | ||||
|     fun get_datetime(datepicker: DatePicker, timepicker: TimePicker, seconds: Int): Date | ||||
|     { | ||||
|         val cal = GregorianCalendar.getInstance() | ||||
|         cal.set(datepicker.year, datepicker.month, datepicker.dayOfMonth, timepicker.hour, timepicker.minute, seconds) | ||||
|         Log.i("VOUSSOIR", cal.time.toString()) | ||||
|         return cal.time | ||||
|     } | ||||
| 
 | ||||
|     fun decrement_datepicker(picker: DatePicker) | ||||
|     { | ||||
|         val cal = GregorianCalendar.getInstance() | ||||
|         cal.set(picker.year, picker.month, picker.dayOfMonth) | ||||
|         cal.add(Calendar.DATE, -1) | ||||
|         picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)) | ||||
|     } | ||||
| 
 | ||||
|     fun increment_datepicker(picker: DatePicker) | ||||
|     { | ||||
|         val cal = GregorianCalendar.getInstance() | ||||
|         cal.set(picker.year, picker.month, picker.dayOfMonth) | ||||
|         cal.add(Calendar.DATE, 1) | ||||
|         picker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)) | ||||
|     } | ||||
| 
 | ||||
|     private fun setupStatisticsViews() | ||||
|     { | ||||
|         // populate views | ||||
|         val stats: TrackStatistics = track.statistics() | ||||
|         trackNameView.text = track.name | ||||
|         distanceView.text = LengthUnitHelper.convertDistanceToString(stats.distance, useImperialUnits) | ||||
|  | @ -153,7 +259,7 @@ data class TrackFragmentLayoutHolder( | |||
|         durationView.text = DateTimeHelper.convertToReadableTime(context, stats.duration) | ||||
|         velocityView.text = LengthUnitHelper.convertToVelocityString(stats.velocity, useImperialUnits) | ||||
|         recordingStartView.text = DateTimeHelper.convertToReadableDateAndTime(track.start_time) | ||||
|         recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.stop_time) | ||||
|         recordingStopView.text = DateTimeHelper.convertToReadableDateAndTime(track.end_time) | ||||
|         maxAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.max_altitude, useImperialUnits) | ||||
|         minAltitudeView.text = LengthUnitHelper.convertDistanceToString(stats.min_altitude, useImperialUnits) | ||||
|         positiveElevationView.text = LengthUnitHelper.convertDistanceToString(stats.total_ascent, useImperialUnits) | ||||
|  | @ -192,5 +298,17 @@ data class TrackFragmentLayoutHolder( | |||
|         return (event != null) | ||||
|     } | ||||
| 
 | ||||
|     private val requery_and_render: Runnable = object : Runnable { | ||||
|         override fun run() | ||||
|         { | ||||
|             Log.i("VOUSSOIR", "requery_and_render") | ||||
|             track.start_time = get_datetime(track_query_start_date, track_query_start_time, seconds=0) | ||||
|             track.end_time = get_datetime(track_query_end_date, track_query_end_time, seconds=59) | ||||
|             track.load_trkpts() | ||||
|             Log.i("VOUSSOIR", "Reloaded ${track.trkpts.size} trkpts.") | ||||
|             render_track() | ||||
|             mapView.invalidate() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,18 +7,81 @@ | |||
|     android:layout_height="match_parent" | ||||
|     tools:context=".MainActivity"> | ||||
| 
 | ||||
|     <!-- OSM MAP --> | ||||
|     <org.osmdroid.views.MapView | ||||
|         android:id="@+id/map" | ||||
|     <androidx.constraintlayout.widget.ConstraintLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_alignParentStart="true" | ||||
|         android:layout_alignParentTop="true" | ||||
|         android:contentDescription="@string/descr_map_last_track" | ||||
|         app:layout_anchor="@+id/map" | ||||
|         app:layout_anchorGravity="center"> | ||||
|         > | ||||
| 
 | ||||
|     </org.osmdroid.views.MapView> | ||||
|         <DatePicker | ||||
|             android:id="@+id/track_query_start_date" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="130dp" | ||||
|             android:translationX="-45dp" | ||||
|             android:translationY="-30dp" | ||||
|             android:calendarViewShown="false" | ||||
|             android:datePickerMode="spinner" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             /> | ||||
| 
 | ||||
|         <TimePicker | ||||
|             android:id="@+id/track_query_start_time" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="130dp" | ||||
|             android:translationX="-150dp" | ||||
|             android:translationY="-30dp" | ||||
|             android:timePickerMode="spinner" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             app:layout_constraintStart_toEndOf="@+id/track_query_start_date" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             /> | ||||
| 
 | ||||
|         <DatePicker | ||||
|             android:id="@+id/track_query_end_date" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="130dp" | ||||
|             android:translationX="-45dp" | ||||
|             android:translationY="-100dp" | ||||
|             android:datePickerMode="spinner" | ||||
|             android:calendarViewShown="false" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/track_query_start_date" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             /> | ||||
| 
 | ||||
|         <TimePicker | ||||
|             android:id="@+id/track_query_end_time" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="130dp" | ||||
|             android:translationX="-150dp" | ||||
|             android:translationY="-100dp" | ||||
|             android:timePickerMode="spinner" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/track_query_start_time" | ||||
|             app:layout_constraintStart_toEndOf="@+id/track_query_start_date" | ||||
|             android:scaleX="0.5" | ||||
|             android:scaleY="0.5" | ||||
|             /> | ||||
| 
 | ||||
|         <org.osmdroid.views.MapView | ||||
|             android:id="@+id/map" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/track_query_end_time" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintBottom_toBottomOf="parent" | ||||
|             android:contentDescription="@string/descr_map_last_track" | ||||
|             > | ||||
| 
 | ||||
|         </org.osmdroid.views.MapView> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     </androidx.constraintlayout.widget.ConstraintLayout> | ||||
| 
 | ||||
|     <!-- BOTTOM SHEET --> | ||||
|     <androidx.core.widget.NestedScrollView | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ | |||
|             app:layout_constraintVertical_bias="0" /> | ||||
| 
 | ||||
|         <!-- ONBOARDING --> | ||||
| 
 | ||||
|         <include | ||||
|             layout="@layout/track_onboarding" | ||||
|             android:layout_width="0dp" | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue