Page Menu
Home
GitPull.it
Search
Configure Global Search
Log In
Files
F13391465
D250.1780171319.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Authored By
Unknown
Size
50 KB
Referenced Files
None
Subscribers
None
D250.1780171319.diff
View Options
diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
--- a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
+++ b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
@@ -17,6 +17,7 @@
*/
package it.reyboz.bustorino.adapters;
+import android.content.Context;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
@@ -63,6 +64,7 @@
//DO THE ACTUAL WORK TO PUT THE DATA
if(stops==null || stops.size() == 0) return; //NO STOPS
final Stop stop = stops.get(position);
+ final Context context = holder.itemView.getContext();
if(stop!=null){
if(stop.getDistanceFromLocation(userPosition)!=Double.POSITIVE_INFINITY){
Double distance = stop.getDistanceFromLocation(userPosition);
@@ -70,13 +72,15 @@
} else {
holder.distancetextView.setVisibility(View.GONE);
}
- holder.stopNameView.setText(stop.getStopDisplayName());
+ holder.stopNameView.setText(context.getString(
+ R.string.two_strings_format,"", stop.getStopDisplayName()));
+ // stop.ID +" - "+ stop.getStopDisplayName());
holder.stopIDView.setText(stop.ID);
String whatStopsHere = stop.routesThatStopHereToString();
if(whatStopsHere == null) {
holder.routesView.setVisibility(View.GONE);
} else {
- holder.routesView.setText(whatStopsHere);
+ holder.routesView.setText(context.getString(R.string.lines_fill, whatStopsHere));
holder.routesView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
}
holder.stopID =stop.ID;
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt
@@ -18,6 +18,7 @@
package it.reyboz.bustorino.fragments
import android.content.Context
+import android.content.res.ColorStateList
import android.location.Location
import android.os.Bundle
import android.util.Log
@@ -27,7 +28,7 @@
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
-import androidx.core.util.Pair
+import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
@@ -45,8 +46,10 @@
import it.reyboz.bustorino.util.Permissions
import it.reyboz.bustorino.util.Permissions.Companion.bothLocationPermissionsGranted
import it.reyboz.bustorino.util.StopSorterByDistance
+import it.reyboz.bustorino.util.ViewUtils
import it.reyboz.bustorino.viewmodels.NearbyStopsViewModel
import java.util.*
+import java.util.concurrent.atomic.AtomicBoolean
class NearbyStopsFragment : ScreenBaseFragment() {
override fun getBaseViewForSnackBar(): View? {
@@ -69,12 +72,12 @@
}
private enum class LocationShowingStatus {
- SEARCHING, POSITION_FOUND, DISABLED, NO_PERMISSION
+ SEARCHING, LOCATION_FOUND, DISABLED, NO_PERMISSION //NO_STOPS_NEARBY
}
private var mListener: FragmentListenerMain? = null
- private var fragment_type = FragType.STOPS
+ private var fragmentType = FragType.STOPS
private lateinit var gridRecyclerView: RecyclerView
@@ -100,24 +103,33 @@
//These are useful for the case of nearby arrivals
private var arrivalsStopAdapter: ArrivalsStopAdapter? = null
- private var currentNearbyStops = ArrayList<Stop>()
+ private var currentNearbyStops: ArrayList<Stop>? = null
private var showingStatus = LocationShowingStatus.NO_PERMISSION
private var isLocationEnabled = false
+ private var dataShownInAdapter = AtomicBoolean(false)
+ private var noDataMessageId = R.string.no_stops_nearby
+ private var loadingDataMessageId = R.string.position_searching_message
+
private val locationUpdateListener: LocationUpdateListener = object : LocationUpdateListener {
override fun onLocationUpdate(location: Location) {
- updateLocationViewModel(location)
+
+ if (location.getAccuracy() < MIN_ACCURACY) {
+ lastPosition = GPSPoint(location.getLatitude(), location.getLongitude())
+ viewModel.setLastLocation(location)
+ }
}
override fun onFusedStatusChanged(isEnabled: Boolean) {
Log.d(DEBUG_TAG, "Location provider is enabled: " + isEnabled)
isLocationEnabled = isEnabled
- if (isEnabled) {
- setShowingStatus(LocationShowingStatus.SEARCHING)
- } else {
- setShowingStatus(LocationShowingStatus.DISABLED)
- }
+ if(!dataShownInAdapter.get())
+ if (isEnabled) {
+ setShowingStatus(LocationShowingStatus.SEARCHING)
+ } else {
+ setShowingStatus(LocationShowingStatus.DISABLED)
+ }
}
}
@@ -199,7 +211,7 @@
switchButton = root.findViewById<AppCompatButton>(R.id.switchButton)
scrollListener = CommonScrollListener(mListener, false)
- switchButton!!.setOnClickListener(View.OnClickListener { v: View? -> switchFragmentType() })
+ switchButton!!.setOnClickListener { v: View? -> switchFragmentType() }
if (BuildConfig.DEBUG) Log.d(DEBUG_TAG, "onCreateView")
val appContext = requireContext().applicationContext
@@ -238,6 +250,8 @@
if (!locationProvider!!.isRunning()) {
startLocationUpdatesByType()
setShowingStatus(LocationShowingStatus.SEARCHING)
+ } else{
+ Log.w(DEBUG_TAG, "Asked to check and start location updates, but provider is already running")
}
} else {
setShowingStatus(LocationShowingStatus.NO_PERMISSION)
@@ -258,23 +272,27 @@
distance = 40
}
if ((stops.size < stopsMinNumber && distance <= stopsMaxDistance)) {
- viewModel.setDistance(distance + 40)
+ viewModel.setDistance(distance + 50)
//viewModel.requestStopsAtDistance(distance, true);
//Log.d(DEBUG_TAG, "Doubling distance now!");
return@observe // THIS WORKS AS AN `else`
}
- if (!stops.isEmpty()) {
- currentNearbyStops = stops
- setShowingStatus(LocationShowingStatus.POSITION_FOUND)
- showStopsInViews(currentNearbyStops, lastPosition)
+ displayStopsOrRequestArrivals(stops)
+ }
+
+ viewModel.locationLiveData.observe(getViewLifecycleOwner()) {loc ->
+ if(loc!=null){
+ setShowingStatus(LocationShowingStatus.LOCATION_FOUND)
}
+ //the stops are updated manually
}
viewModel.downloadingArrivals.observe(viewLifecycleOwner){ running ->
+ flatProgressBar.isIndeterminate = true
if(!running) flatProgressBar.visibility = View.GONE
else flatProgressBar.visibility = View.VISIBLE
}
- viewModel.progressPerc.observe(viewLifecycleOwner){ progress ->
+ /*viewModel.progressPerc.observe(viewLifecycleOwner){ progress ->
flatProgressBar.isIndeterminate = false
flatProgressBar.progress = progress
flatProgressBar.max = 100
@@ -285,34 +303,65 @@
}
+ */
+
viewModel.arrivalsDecoupled.observe(viewLifecycleOwner){ stoprouteList ->
if (getContext() == null) {
Log.e(DEBUG_TAG, "Trying to show arrivals in Recycler but we're not attached")
return@observe
}
- val context = requireContext()
- if (firstLocForArrivals) {
- mListener?.let{
- lastPosition?.let{ pos ->
- arrivalsStopAdapter = ArrivalsStopAdapter(stoprouteList, it, context, pos)
- gridRecyclerView.setAdapter(arrivalsStopAdapter)
- firstLocForArrivals = false
- }
- }
- } else {
- lastPosition?.let{ pos ->
- arrivalsStopAdapter?.setRoutesPairListAndPosition(stoprouteList, pos)
- }
- }
+ showArrivals(stoprouteList)
//arrivalsStopAdapter.notifyDataSetChanged();
- setShowingStatus(LocationShowingStatus.POSITION_FOUND)
- showRecyclerHidingLoadMessage()
+ //showRecyclerHidingLoadMessage()
//if (mListener != null) mListener!!.readyGUIfor(FragmentKind.NEARBY_ARRIVALS)
}
//added
- checkPermissionLocationStart()
+ //checkPermissionLocationStart()
+ }
+
+ private fun displayStopsOrRequestArrivals(stops: ArrayList<Stop>){
+ if (!stops.isEmpty()) {
+ currentNearbyStops = stops
+ //displayStopsOrLaunchArrivalsRequest(stops, lastPosition)
+ viewModel.locationLiveData.value?.let{loc ->
+ when(fragmentType){
+ FragType.STOPS->{
+
+ lastPosition?.let{loc ->
+ Collections.sort(stops, StopSorterByDistance(loc))
+ showStopsInRecycler(stops,loc)
+ }
+
+ }
+ FragType.ARRIVALS -> {
+ viewModel.requestArrivalsForStops(stops)
+ }
+ }
+ }
+ } else{
+ showNoStopsMessage()
+ viewModel.cancelAllArrivalsRequests()
+ }
+ }
+
+ private fun showArrivals(stoprouteList: ArrayList<RouteWithStop>){
+ val context = requireContext()
+ if (firstLocForArrivals) {
+ mListener?.let{
+ lastPosition?.let{ pos ->
+ arrivalsStopAdapter = ArrivalsStopAdapter(stoprouteList, it, context, pos)
+ gridRecyclerView.setAdapter(arrivalsStopAdapter)
+ firstLocForArrivals = false
+ }
+ }
+ } else {
+ lastPosition?.let{ pos ->
+ arrivalsStopAdapter?.setRoutesPairListAndPosition(stoprouteList, pos)
+ }
+ }
+ dataShownInAdapter.set(true)
}
@@ -320,7 +369,7 @@
* Internal bit used to start location updates
*/
private fun startLocationUpdatesByType() {
- when (fragment_type) {
+ when (fragmentType) {
FragType.STOPS -> locationProvider!!.startUpdates(locationOptionsStops)
FragType.ARRIVALS -> locationProvider!!.startUpdates(locationOptionsArrivals)
}
@@ -332,21 +381,11 @@
* @param type the type, TYPE_ARRIVALS or TYPE_STOPS
*/
private fun setFragmentType(type: FragType) {
- val isChanged = fragment_type != type
- this.fragment_type = type
- /*switch(type){
- case ARRIVALS:
- TIME_INTERVAL_REQUESTS = 5*1000;
- break;
- case STOPS:
- TIME_INTERVAL_REQUESTS = 1000;
-
- }
+ val isChanged = fragmentType != type
+ this.fragmentType = type
- */
if (isChanged) {
startLocationUpdatesByType()
- setShowingStatus(LocationShowingStatus.SEARCHING)
}
}
@@ -354,14 +393,13 @@
* Set the location in the view model if it is good
* @param location new location
*/
+ /*
private fun updateLocationViewModel(location: Location, accuracy: Float = 150f) {
- if (location.getAccuracy() < accuracy) {
- lastPosition = GPSPoint(location.getLatitude(), location.getLongitude())
- //viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true);
- viewModel.setLastLocation(location)
- }
+
}
+ */
+
private fun setShowingStatus(newStatus: LocationShowingStatus) {
var newStatus = newStatus
if (newStatus == showingStatus) {
@@ -375,17 +413,17 @@
}
when (newStatus) {
- LocationShowingStatus.POSITION_FOUND -> {
+ LocationShowingStatus.LOCATION_FOUND -> {
circlingProgressBar!!.setVisibility(View.GONE)
loadingTextView!!.setVisibility(View.GONE)
gridRecyclerView.setVisibility(View.VISIBLE)
messageTextView!!.setVisibility(View.GONE)
enableLocationButton.setVisibility(View.GONE)
-
}
LocationShowingStatus.NO_PERMISSION -> {
circlingProgressBar?.setVisibility(View.GONE)
+ flatProgressBar.setVisibility(View.GONE)
loadingTextView?.setVisibility(View.GONE)
messageTextView?.setText(R.string.enable_position_message_nearby)
messageTextView?.setVisibility(View.VISIBLE)
@@ -396,18 +434,22 @@
//if (showingStatus== LocationShowingStatus.SEARCHING){
circlingProgressBar!!.setVisibility(View.GONE)
loadingTextView!!.setVisibility(View.GONE)
+ flatProgressBar.setVisibility(View.GONE)
//}
messageTextView!!.setText(R.string.enable_location_message)
messageTextView!!.setVisibility(View.VISIBLE)
enableLocationButton.setVisibility(View.GONE)
}
-
LocationShowingStatus.SEARCHING -> {
circlingProgressBar!!.setVisibility(View.VISIBLE)
- loadingTextView!!.setVisibility(View.VISIBLE)
+ flatProgressBar.setVisibility(View.GONE)
gridRecyclerView.setVisibility(View.GONE)
messageTextView!!.setVisibility(View.GONE)
enableLocationButton.setVisibility(View.GONE)
+ loadingTextView?.apply {
+ setText(loadingDataMessageId)
+ visibility = View.VISIBLE
+ }
}
}
showingStatus = newStatus
@@ -440,8 +482,48 @@
override fun onResume() {
super.onResume()
//fix view if we were showing the stops or the arrivals
- prepareForFragmentType()
- when (fragment_type) {
+ loadPreferencesStops()
+ setGuiForFragmentType(fragmentType)
+ //if(lastPosition == null){
+ viewModel.locationLiveData.value?.let{loc -> lastPosition = loc }
+ //}
+
+ if(bothLocationPermissionsGranted(requireContext())){
+ locationProvider?.apply {
+ if(isLocationEnabled()){
+ //location is enabled, start updates
+ startLocationUpdatesByType()
+ if(lastPosition == null){
+ setShowingStatus(LocationShowingStatus.SEARCHING)
+ }
+ } else{
+ setShowingStatus(LocationShowingStatus.DISABLED)
+ }
+ }
+
+ } else{
+ setShowingStatus(LocationShowingStatus.NO_PERMISSION)
+ }
+ //setupDataAndLayoutByFragmentType()
+
+ mListener!!.enableRefreshLayout(false)
+
+ if(fragmentType == FragType.ARRIVALS){
+ viewModel.arrivalsDecoupled.value?.let{
+ //re-do the adapter
+ firstLocForArrivals = true
+ showArrivals(it)
+ }
+ } else if(fragmentType == FragType.STOPS) {
+ viewModel.stopsAtDistance.value?.let {
+ //remake the adapter
+ firstLocForStops = true
+ displayStopsOrRequestArrivals(it)
+ }
+ }
+
+ /*
+ when (fragmentType) {
FragType.STOPS -> if (dataAdapter != null) {
//gridRecyclerView.setAdapter(dataAdapter);
circlingProgressBar!!.setVisibility(View.GONE)
@@ -455,15 +537,20 @@
}
}
- mListener!!.enableRefreshLayout(false)
+ */
+
Log.d(DEBUG_TAG, "OnResume called")
if (getContext() == null) {
Log.e(DEBUG_TAG, "NULL CONTEXT, everything is going to crash now")
stopsMinNumber = 5
stopsMaxDistance = 600
- return
+
}
//Re-read preferences
+
+
+ }
+ private fun loadPreferencesStops(){
val shpr = PreferenceManager.getDefaultSharedPreferences(requireContext().getApplicationContext())
//For some reason, they are all saved as strings
stopsMaxDistance = shpr.getInt(getString(R.string.pref_key_radius_recents), 600)
@@ -482,8 +569,6 @@
DEBUG_TAG,
"Max distance for stops: $stopsMaxDistance, Min number of stops: $stopsMinNumber"
)
-
- //checkPermissionLocationStart()
}
@@ -504,143 +589,198 @@
/**
* Display the stops, or run new set of requests for arrivals
*/
- private fun showStopsInViews(stops: ArrayList<Stop>, location: GPSPoint?) {
+ /*private fun displayStopsOrLaunchArrivalsRequest(stops: ArrayList<Stop>, location: GPSPoint) {
if (stops.isEmpty()) {
setNoStopsLayout()
return
}
- if (location == null) {
- // we could do something better, but it's better to do this for now
- return
- }
-
- /*var minDistance = Double.POSITIVE_INFINITY
- for (s in stops) {
- minDistance = min(minDistance, s.getDistanceFromLocation(location.getLatitude(), location.getLongitude()))
- }
-
- */
-
-
//quick trial to hopefully always get the stops in the correct order
- Collections.sort<Stop?>(stops, StopSorterByDistance(location))
+
when (fragment_type) {
- FragType.STOPS -> showStopsInRecycler(stops)
+ FragType.STOPS -> {
+ setShowingStatus(LocationShowingStatus.STOPS_FOUND)
+ showStopsInRecycler(stops, location)
+ }
FragType.ARRIVALS -> {
- //don't do anything if we're not attached
- /*context?.let{
- if (arrivalsManager == null) arrivalsManager =
- NearbyArrivalsDownloader(it.applicationContext, arrivalsListener)
- arrivalsManager!!.requestArrivalsForStops(stops)
- }
-
- */
viewModel.requestArrivalsForStops(stops)
+ //setShowingStatus(LocationShowingStatus.SEARCHING)
+ viewModel.arrivalsDecoupled.value?.let{
+
+ }
}
}
}
- /**
- * To enable targeting from the Button
*/
- fun switchFragmentType(v: View?) {
- switchFragmentType()
- }
+
/**
* Call when you need to switch the type of fragment
*/
private fun switchFragmentType() {
- when (fragment_type) {
- FragType.ARRIVALS -> setFragmentType(FragType.STOPS)
- FragType.STOPS -> setFragmentType(FragType.ARRIVALS)
- else -> {}
+ when (fragmentType) {
+ FragType.ARRIVALS -> {
+ viewModel.cancelAllArrivalsRequests()
+ setFragmentType(FragType.STOPS)
+ //when switching from arrivals
+ firstLocForStops = true
+ }
+ FragType.STOPS -> {
+ setFragmentType(FragType.ARRIVALS)
+ firstLocForArrivals = true
+ }
+ }
+ //now it's switched
+ setGuiForFragmentType(fragmentType)
+
+ viewModel.stopsAtDistance.value?.let{
+ //re-issue update, triggering chain
+ viewModel.stopsAtDistance.value = it
+ }
+ /*if(fragmentType == FragType.ARRIVALS) {
+ viewModel.stopsAtDistance.value?.let{
+ viewModel.requestArrivalsForStops(it)
+ }
}
- prepareForFragmentType()
- //locManager.removeLocationRequestFor(fragmentLocationListener);
- //locManager.addLocationRequestFor(fragmentLocationListener);
- if (lastPosition != null) {
- // we have at least one fix on the position
- showStopsInViews(currentNearbyStops, lastPosition)
+
+ */
+ //
+ //setupDataAndLayoutByFragmentType()
+ }
+ private fun setGuiForFragmentType(fragmentType: FragType) {
+ when (fragmentType) {
+ FragType.STOPS ->{
+ switchButton!!.text = getString(R.string.show_arrivals)
+ titleTextView!!.text = getString(R.string.nearby_stops_message)
+ noDataMessageId = R.string.no_stops_nearby
+ loadingDataMessageId = R.string.position_searching_message
+ //switchButton?.backgroundTintList = ColorStateList.valueOf(
+ // ViewUtils.getColorFromTheme(requireContext(), R.attr.colorPrimaryDark))
+
+ }
+ FragType.ARRIVALS ->{
+ titleTextView!!.text = getString(R.string.nearby_arrivals_message)
+ switchButton!!.text = getString(R.string.show_stops)
+ noDataMessageId = R.string.no_stops_nearby_arrivals
+ loadingDataMessageId = R.string.searching_arrivals_indefinite
+ //switchButton?.backgroundTintList = ColorStateList.valueOf(
+ // ResourcesCompat.getColor(resources, R.color.light_blue_900, requireActivity().theme))
+ }
}
}
/**
* Prepare the views for the set fragment type
*/
- private fun prepareForFragmentType() {
- if (fragment_type == FragType.STOPS) {
+ /*private fun setupDataAndLayoutByFragmentType() {
+
+ var dataAvailable = false
+ if (fragmentType == FragType.STOPS) {
switchButton!!.text = getString(R.string.show_arrivals)
titleTextView!!.text = getString(R.string.nearby_stops_message)
-
- dataAdapter?.let{ gridRecyclerView.adapter = it
-
+ viewModel.stopsAtDistance.value?.let { stops->
+ // if data adapter is not null set stops, otherwise
+ dataAdapter?.setStops(stops) ?: lastPosition?.let{ pos ->
+ dataAdapter = SquareStopAdapter(stops, mListener, pos)
+ }
+ Log.d(DEBUG_TAG, "Found ${stops.size} stops")
+ }
+ dataAdapter?.let{
+ gridRecyclerView.adapter = it
+ dataAvailable = true
}
-
mListener?.readyGUIfor(FragmentKind.NEARBY_STOPS)
- } else if (fragment_type == FragType.ARRIVALS) {
+ } else if (fragmentType == FragType.ARRIVALS) {
titleTextView!!.text = getString(R.string.nearby_arrivals_message)
switchButton!!.text = getString(R.string.show_stops)
val arrivalsSorted = viewModel.arrivalsDecoupled.value
arrivalsSorted?.let{
- lastPosition?.let{pos ->
- arrivalsStopAdapter = ArrivalsStopAdapter(it,mListener!!,requireContext(), pos )
- }
+ arrivalsStopAdapter?.setRoutesPairListAndPosition(
+ it, lastPosition) ?: lastPosition?.let{pos ->
+ arrivalsStopAdapter = ArrivalsStopAdapter(it, mListener!!, requireContext(), pos)
+ }
}
+
arrivalsStopAdapter?.let{
gridRecyclerView.setAdapter(it)
}
-
mListener?.readyGUIfor(FragmentKind.NEARBY_ARRIVALS)
}
+ if(gridRecyclerView.adapter == null){
+ flatProgressBar.isIndeterminate = true
+ flatProgressBar.visibility = View.VISIBLE
+ } else{
+ flatProgressBar.visibility = View.GONE
+ flatProgressBar.isIndeterminate = false
+ }
+
}
+ */
+
//useful methods
/**//// GUI METHODS //////// */
- private fun showStopsInRecycler(stops: MutableList<Stop>?) {
+ private fun showStopsInRecycler(stops: MutableList<Stop>, location: GPSPoint) {
+ // hide the progress bar
+ flatProgressBar.visibility = View.GONE
+
+ Collections.sort(stops, StopSorterByDistance(location))
if (dataAdapter == null) {
dataAdapter = SquareStopAdapter(stops, mListener, lastPosition)
- gridRecyclerView!!.setAdapter(dataAdapter)
firstLocForStops = false
} else {
dataAdapter!!.setUserPosition(lastPosition)
dataAdapter!!.setStops(stops)
}
+ gridRecyclerView.setAdapter(dataAdapter)
+
+ if(gridRecyclerView.visibility != View.VISIBLE){
+ if(showingStatus == LocationShowingStatus.LOCATION_FOUND)
+ Log.e(DEBUG_TAG, "Visualization error: the recyclerView is not visible but location status is $showingStatus")
+ else{
+ Log.w(DEBUG_TAG, "Grid recyclerView should be visible for the stops, setting status ${LocationShowingStatus.LOCATION_FOUND}")
+ setShowingStatus(LocationShowingStatus.LOCATION_FOUND)
+ }
+ }
+ dataShownInAdapter.set(true)
//showRecyclerHidingLoadMessage();
- if (gridRecyclerView!!.getVisibility() != View.VISIBLE) {
+ /*if (gridRecyclerView!!.getVisibility() != View.VISIBLE) {
circlingProgressBar!!.setVisibility(View.GONE)
loadingTextView!!.setVisibility(View.GONE)
gridRecyclerView!!.setVisibility(View.VISIBLE)
}
messageTextView!!.setVisibility(View.GONE)
- }
-
- private fun showArrivalsInRecycler(routesPairList: List<Pair<Stop, Route>>) {
-
+ */
}
- private fun setNoStopsLayout() {
- messageTextView!!.setVisibility(View.VISIBLE)
- messageTextView!!.setText(R.string.no_stops_nearby)
- circlingProgressBar!!.setVisibility(View.GONE)
- loadingTextView!!.setVisibility(View.GONE)
- }
-
/**
* Does exactly what is says on the tin
*/
- private fun showRecyclerHidingLoadMessage() {
+ /*private fun showRecyclerHidingLoadMessage() {
if (gridRecyclerView.getVisibility() != View.VISIBLE) {
circlingProgressBar!!.setVisibility(View.GONE)
loadingTextView!!.setVisibility(View.GONE)
gridRecyclerView.setVisibility(View.VISIBLE)
}
messageTextView!!.setVisibility(View.GONE)
- } /*
+ }
+
+ */
+
+ private fun showNoStopsMessage(){
+ messageTextView!!.setVisibility(View.VISIBLE)
+ messageTextView!!.setText(noDataMessageId)
+ flatProgressBar.visibility = View.GONE
+ circlingProgressBar!!.setVisibility(View.GONE)
+ loadingTextView!!.setVisibility(View.GONE)
+ enableLocationButton.setVisibility(View.GONE)
+ }
+
+ /*
* Local locationListener, to use for the GPS
*/
/*
@@ -694,6 +834,7 @@
const val FRAGMENT_TAG: String = "NearbyStopsFrag"
const val COLUMN_WIDTH_DP: Int = 250
+ const val MIN_ACCURACY = 200.0
/**
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt
@@ -54,6 +54,8 @@
val progressPerc = MutableLiveData<Int>()
+ //val fragmentType = MutableLiveData<NearbyStopsFragment.FragType>()
+
val downloadingArrivals = MutableLiveData<Boolean>()
val lastTimeFinished = AtomicLong(0)
private var job : Job? = null
@@ -134,13 +136,16 @@
private val errorRequests = AtomicInteger(0)
private val runningRequests = AtomicInteger(0)
+ fun cancelAllArrivalsRequests() {
+ volleyManager.requestQueue.cancelAll(REQUEST_TAG)
+ }
/**
* Run new batch of requests
*/
fun requestArrivalsForStops(stops: List<Stop>) {
//nearbyArrivalsDownloader.requestArrivalsForStops(stops)
if(runningRequests.get() > 0) {
- volleyManager.requestQueue.cancelAll(REQUEST_TAG)
+ cancelAllArrivalsRequests()
}
val currentDate = Date()
val timeRange = 3600
@@ -339,7 +344,7 @@
}
override fun onCleared() {
- volleyManager.requestQueue.cancelAll(REQUEST_TAG)
+ cancelAllArrivalsRequests()
super.onCleared()
}
diff --git a/app/src/main/res/layout/fragment_nearby_stops.xml b/app/src/main/res/layout/fragment_nearby_stops.xml
--- a/app/src/main/res/layout/fragment_nearby_stops.xml
+++ b/app/src/main/res/layout/fragment_nearby_stops.xml
@@ -1,65 +1,102 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="it.reyboz.bustorino.fragments.NearbyStopsFragment">
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ tools:context="it.reyboz.bustorino.fragments.NearbyStopsFragment"
+>
<TextView
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/nearby_stops_message" android:id="@+id/titleTextView"
android:textAppearance="@android:style/TextAppearance.Medium"
android:layout_marginBottom="6dp"
android:layout_marginTop="15dp"
android:paddingTop="3dp"
- android:gravity="center_horizontal"
android:fontFamily="@font/lato_bold"
android:textSize="23sp"
- android:layout_toLeftOf="@id/switchButton" android:layout_marginLeft="10dp"
- android:layout_marginStart="10dp" android:layout_marginRight="10dp" android:layout_marginEnd="10dp"
+ android:gravity="center_vertical"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="10dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/switchButton"
+ app:layout_constraintBottom_toBottomOf="@id/switchButton"
+
/>
<androidx.appcompat.widget.AppCompatButton
xmlns:app="http://schemas.android.com/apk/res-auto"
android:text="@string/show_arrivals"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:id="@+id/switchButton"
+ android:layout_height="wrap_content"
+ android:id="@+id/switchButton"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
- android:layout_margin="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="10dp"
app:backgroundTint="@color/blue_500"
android:textColor="@android:color/white"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ />
+ <androidx.constraintlayout.widget.Barrier
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/nearbyBarrier"
+ android:layout_marginTop="5dp"
+ app:constraint_referenced_ids="titleTextView,switchButton"
+ app:barrierDirection="bottom"
/>
+
<ProgressBar
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
- android:layout_width="6dp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/horizontalProgressBar"
- android:layout_alignParentEnd="true"
- android:layout_alignParentStart="true"
- android:layout_below="@id/titleTextView"
+ app:layout_constraintTop_toBottomOf="@id/nearbyBarrier"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
android:indeterminate="true"
android:visibility="gone"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
- />
+ android:layout_marginBottom="5dp"
+ app:layout_constraintBottom_toTopOf="@id/nearbyTopSeparator"
+ />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@color/separator"
+ android:id="@+id/nearbyTopSeparator"
+ app:layout_constraintTop_toBottomOf="@id/nearbyBarrier"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="5dp"
+ android:layout_marginEnd="5dp"
+ />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/horizontalProgressBar"
+ android:layout_height="0dp"
android:id="@+id/stopGridRecyclerView"
android:clickable="true"
android:focusable="true"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="0dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:visibility="gone"
android:clipChildren="false"
android:clipToPadding="true"
android:horizontalSpacing="10dp"
- android:layout_alignParentBottom="true"
- android:verticalSpacing="10dp"/>
+ android:verticalSpacing="10dp"
+ app:layout_constraintTop_toBottomOf="@id/nearbyTopSeparator"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
<ProgressBar
@@ -74,16 +111,22 @@
android:layout_below="@+id/titleTextView"
android:layout_centerHorizontal="true"
android:visibility="gone"
+
+ app:layout_constraintTop_toBottomOf="@id/nearbyBarrier"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/positionLoadingTextView"
- android:layout_below="@id/circularProgressBar"
android:text="@string/position_searching_message"
android:layout_marginTop="8dp"
android:textSize="15sp"
android:layout_centerHorizontal="true"
android:visibility="gone"
+ app:layout_constraintTop_toBottomOf="@id/circularProgressBar"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
/>
<TextView
android:text="@string/enable_position_message_nearby"
@@ -94,12 +137,15 @@
android:visibility="visible"
android:textAppearance="@style/Base.ThemeOverlay.AppCompat.Light"
android:layout_below="@+id/titleTextView"
- android:layout_centerHorizontal="true"
android:textSize="17sp"
android:layout_marginTop="20dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:textColor="?android:textColorSecondary"
+
+ app:layout_constraintTop_toBottomOf="@id/nearbyBarrier"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
/>
<com.google.android.material.button.MaterialButton
@@ -111,8 +157,12 @@
android:visibility="visible"
android:id="@+id/grantLocationButton"
android:layout_marginTop="15dp"
+
+ app:layout_constraintTop_toBottomOf="@id/messageTextView"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
/>
-</RelativeLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/item_arrivals_nearby_card.xml b/app/src/main/res/layout/item_arrivals_nearby_card.xml
--- a/app/src/main/res/layout/item_arrivals_nearby_card.xml
+++ b/app/src/main/res/layout/item_arrivals_nearby_card.xml
@@ -12,7 +12,8 @@
app:cardCornerRadius="6dp"
app:strokeColor="@color/card_border"
app:strokeWidth="1dp"
- android:layout_margin="5dp" android:padding="6dp">
+ android:layout_margin="5dp" android:padding="6dp"
+>
<RelativeLayout
@@ -27,7 +28,7 @@
android:minWidth="40dp"
android:id="@+id/lineNameTextView"
android:text="10"
- android:textSize="22sp"
+ android:textSize="21sp"
android:textColor="@color/orange_700_display"
android:layout_marginStart="8dp"
android:gravity="center"
@@ -36,7 +37,7 @@
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignBottom="@id/lineDirectionTextView"
-
+ android:fontFamily="@font/lato_bold"
/>
<TextView
@@ -44,7 +45,8 @@
android:layout_height="wrap_content"
android:id="@+id/lineDirectionTextView"
android:text="MADONNA DEL PILONE, LARGO TABACCHI"
- android:textSize="20sp"
+ android:textSize="19sp"
+ android:fontFamily="@font/lato_regular"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_toStartOf="@id/arrivalsDistanceTextView"
android:layout_toEndOf="@id/lineNameTextView"
@@ -95,17 +97,18 @@
android:layout_marginStart="6dp"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
- <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="10dp"
android:layout_marginEnd="10dp"
- android:id="@+id/arrivalsDistanceTextView" android:text="200 m" android:textSize="15sp"
+ android:id="@+id/arrivalsDistanceTextView"
+ android:text="200 m" android:textSize="15sp"
android:layout_marginStart="5dp"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
/>
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_stop_nearby_card.xml b/app/src/main/res/layout/item_stop_nearby_card.xml
--- a/app/src/main/res/layout/item_stop_nearby_card.xml
+++ b/app/src/main/res/layout/item_stop_nearby_card.xml
@@ -1,124 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.cardview.widget.CardView
- xmlns:card_view="http://schemas.android.com/apk/res-auto"
+<com.google.android.material.card.MaterialCardView
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stop_cardView"
- android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- card_view:cardCornerRadius="5dp"
- android:layout_margin="5dp" android:padding="4dp">
+ app:cardCornerRadius="5dp"
+ app:strokeWidth="1dp"
+ app:strokeColor="@color/card_border"
+ app:cardBackgroundColor="@color/card_background"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="8dp"
+ android:layout_marginStart="5dp"
+ android:layout_marginEnd="5dp"
+ android:padding="4dp">
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="match_parent" android:gravity="center_vertical">
- <RelativeLayout
+ android:layout_height="match_parent"
+ android:padding="2dp"
+ >
+ <TextView
+ android:id="@+id/stop_numberText"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:layout_weight="1">
+ android:layout_height="wrap_content"
+ android:text="7261"
+ android:textSize="19sp"
+ android:textIsSelectable="true"
+ android:fontFamily="@font/lato_bold"
+ android:textColor="@color/light_blue_900"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginBottom="6dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="13dp"
+ android:layout_marginEnd="20dp"/>
<TextView
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="Title"
+ android:text="STOP"
android:id="@+id/stop_nameText"
+ android:fontFamily="@font/lato_regular"
style="@style/TextAppearance.AppCompat.Medium"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:paddingLeft="6dp"
- android:textSize="20sp"
+ android:textSize="19sp"
android:textColor="?android:textColorPrimary"
android:layout_marginBottom="6dp"
- android:layout_marginTop="6dp" android:layout_marginLeft="6dp"
- android:layout_marginStart="6dp" android:layout_marginRight="6dp" android:layout_marginEnd="6dp"/>
- <TextView
- android:id="@+id/stop_numberText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="stopNumber"
- android:textSize="18sp"
- android:layout_below="@id/stop_nameText"
- android:textIsSelectable="true" android:layout_marginTop="6dp"
- android:textColor="?colorPrimaryDark"
- android:layout_marginStart="12dp" android:layout_marginEnd="8dp"
- android:layout_marginBottom="8dp"/>
- <TextView
- android:text="200m"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="6dp"
- android:layout_marginStart="10dp"
- android:minWidth="50dp"
- android:textSize="16sp"
- android:id="@+id/stop_distanceTextView"
- android:layout_toEndOf="@+id/stop_numberText"
- android:layout_toRightOf="@id/stop_numberText"
- android:layout_alignTop="@+id/stop_numberText"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="1dp"
+ android:layout_marginEnd="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/stop_numberText"
+ app:layout_constraintEnd_toStartOf="@id/stop_distanceTextView"
/>
+ <TextView
+ android:text="200m"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="12dp"
+ android:textSize="15sp"
+ android:gravity="center_vertical"
+ android:id="@+id/stop_distanceTextView"
+ android:fontFamily="@font/lato_regular"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
<!--
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" android:id="@+id/row1" android:layout_below="@+id/stopTitle">
- <TextView
- android:id="@+id/txtLine1_1"
- android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:text="Name"
- android:gravity="center_vertical"
- android:textSize="14sp"
- android:layout_marginTop="10dp"
- android:layout_marginLeft="5dp" android:layout_margin="3dp"/>
- <TextView
- android:id="@+id/txtPass1_1"
- android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:text="Name"
- android:gravity="center_vertical"
-
- android:layout_marginTop="10dp"
- android:layout_marginLeft="5dp" android:layout_margin="3dp"/>
- <TextView
- android:id="@+id/txtPass2"
- android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:text="Name"
- android:gravity="center_vertical"
- android:textSize="14sp"
- android:layout_marginTop="10dp"
- android:layout_marginLeft="5dp" android:layout_margin="3dp"/>
- <TextView
- android:id="@+id/txtPass3"
- android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:text="Name"
- android:gravity="center_vertical"
- android:textSize="14sp"
- android:layout_marginTop="10dp"
- android:layout_marginLeft="5dp" android:layout_margin="3dp"/>
- </LinearLayout>
- -->
- </RelativeLayout>
- <TextView
+ -->
+ <!--<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:id="@+id/linesText"
+ android:text="@string/lines"
+ android:fontFamily="@font/nevermind_compact"
+ android:textSize="17sp"
+ app:layout_constraintTop_toBottomOf="@id/stop_nameText"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="12dp"
+ />-->
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
android:text="1,2,3,4"
android:id="@+id/stop_linesText"
- android:layout_toRightOf="@+id/stop_distanceTextView"
- android:minWidth="80dp"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true" android:layout_alignBottom="@+id/stop_distanceTextView"
- android:layout_alignTop="@+id/stop_nameText"
android:nestedScrollingEnabled="true"
- android:gravity="center_horizontal|center"
- android:textColor="@color/lines_nearby_color"
- android:fontFamily="@font/nevermind_compact"
+ android:gravity="center_vertical|start"
android:textSize="16sp"
- android:layout_weight="1"
- android:padding="5dp" android:maxWidth="100sp" android:layout_gravity="center_vertical|end"
- android:layout_marginRight="5dp"/>
- </LinearLayout>
-</androidx.cardview.widget.CardView>
\ No newline at end of file
+ android:fontFamily="@font/lato_regular"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="10dp"
+ android:layout_marginEnd="15dp"
+
+ app:layout_constraintTop_toBottomOf="@id/stop_nameText"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/stop_distanceTextView"
+ android:layout_marginStart="50dp"
+
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -22,6 +22,7 @@
<color name="orange_700_display">@color/orange_700_night</color>
<color name="message_background_on_black">#666666</color>
+ <color name="separator">@color/grey_800</color>
<!-- Proposal: change the grey_100 so it's less intense
<color name="grey_100">#d9d9d9</color> --> <!-- Also 42*3 -->
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -9,6 +9,8 @@
<color name="orange_700_40light">#cc6600</color>
<color name="orange_700_30light">#994d00</color>
<color name="blue_500">#2196F3</color>
+ <color name="blue_500_night">#0b6fc1</color>
+
<color name="blue_620">#2a65e8</color>
<color name="blue_700">#2060dd</color>
<color name="brown_vd">#8A4247</color><!-- #1976D2 -->
@@ -84,6 +86,7 @@
<color name="orange_700_display">@color/orange_700</color>
<color name="message_background_on_black">@color/grey_200</color>
<color name="card_background">@color/grey_050</color>
+ <color name="separator">@color/grey_200</color>
<color name="urban_bus_bg">@color/orange_500</color>
<color name="extraurban_bus_bg">@color/blue_extraurbano</color>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -163,6 +163,8 @@
<string name="position_searching_message">Finding location</string>
<string name="no_stops_nearby">No stops nearby</string>
+ <string name="searching_arrivals_indefinite">Loading arrival times</string>
+ <string name="no_stops_nearby_arrivals">No stops nearby to search arrival times for</string>
<string name="pref_num_elements">Minimum number of stops</string>
<string name="main_menu_pref">Preferences</string>
<string name="title_activity_settings">Settings</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -98,6 +98,9 @@
<item name="windowNoTitle">true</item>
</style>
+ <style name="AppThemeDayNight.Card" parent="AppThemeDayNight">
+ <item name="android:fontFamily">@font/lato_regular</item>
+ </style>
</resources>
\ No newline at end of file
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 30, 22:01 (23 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1930247
Default Alt Text
D250.1780171319.diff (50 KB)
Attached To
Mode
D250: Fix nearby fragments visualization bugs, update layout of items
Attached
Detach File
Event Timeline