Page Menu
Home
GitPull.it
Search
Configure Global Search
Log In
Files
F13503027
D221.1782245225.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Authored By
Unknown
Size
90 KB
Referenced Files
None
Subscribers
None
D221.1782245225.diff
View Options
diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -134,7 +134,7 @@
implementation "androidx.coordinatorlayout:coordinatorlayout:1.3.0"
- implementation 'org.jsoup:jsoup:1.21.2'
+ implementation 'org.jsoup:jsoup:1.22.1'
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
implementation 'com.android.volley:volley:1.2.1'
//maplibre
@@ -149,14 +149,13 @@
implementation "ch.acra:acra-mail:$acra_version"
implementation "ch.acra:acra-dialog:$acra_version"
// google transit realtime
- implementation 'com.google.protobuf:protoc:4.33.0'
-
- implementation 'com.google.protobuf:protobuf-javalite:4.33.0'
+ implementation 'com.google.protobuf:protoc:4.34.1'
+ implementation 'com.google.protobuf:protobuf-javalite:4.34.1'
// mqtt library
//implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
//implementation 'com.github.hannesa2:paho.mqtt.android:4.4'
- implementation("com.hivemq:hivemq-mqtt-client:1.3.10")
- implementation(platform("com.hivemq:hivemq-mqtt-client-websocket:1.3.10"))
+ implementation("com.hivemq:hivemq-mqtt-client:1.3.13")
+ implementation(platform("com.hivemq:hivemq-mqtt-client-websocket:1.3.13"))
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
@@ -173,15 +172,9 @@
//multidex - we need this to build the app
implementation "androidx.multidex:multidex:$multidex_version"
- implementation 'de.siegmar:fastcsv:2.2.2'
+ implementation 'de.siegmar:fastcsv:4.2.0'
testImplementation 'junit:junit:4.13.2'
- implementation 'junit:junit:4.13.2'
-
- implementation "androidx.test.ext:junit:1.3.0"
- implementation "androidx.test:core:$androidXTestVersion"
- implementation "androidx.test:runner:$androidXTestVersion"
- implementation "androidx.room:room-testing:$room_version"
androidTestImplementation "androidx.test.ext:junit:1.3.0"
androidTestImplementation "androidx.test:core:$androidXTestVersion"
diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
--- a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
@@ -80,7 +80,7 @@
}
@Override
- public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom){
+ public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom){
readyGUIfor(FragmentKind.LINES);
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
@@ -89,6 +89,16 @@
tr.addToBackStack("LineonMap-"+routeGtfsId);
tr.commit();
+ }
+ @Override
+ public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) {
+ readyGUIfor(FragmentKind.LINES);
+ FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
+ tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
+ LinesDetailFragment.Companion.makeArgsPattern(routeGtfsId, optionalPatternId, args));
+ tr.addToBackStack("Line-"+routeGtfsId);
+ tr.commit();
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
--- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
@@ -24,18 +24,15 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
-import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.graphics.Insets;
import androidx.core.view.*;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -729,17 +726,26 @@
mNavView.setCheckedItem(R.id.nav_arrivals);
}
@Override
- public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom){
+ public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom){
readyGUIfor(FragmentKind.LINES);
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
LinesDetailFragment.Companion.makeArgs(routeGtfsId, stopIDFrom));
- tr.addToBackStack("LineonMap-"+routeGtfsId);
+ tr.addToBackStack("LineFromStop-"+routeGtfsId);
tr.commit();
+ }
+ @Override
+ public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) {
+ readyGUIfor(FragmentKind.LINES);
+ FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
+ tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
+ LinesDetailFragment.Companion.makeArgsPattern(routeGtfsId, optionalPatternId, args));
+ tr.addToBackStack("LineFromOther-"+routeGtfsId);
+ tr.commit();
}
@Override
diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
--- a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
+++ b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
@@ -286,7 +286,7 @@
final String name = r.getName();
final String destination = r.destinazione;
if (name!= null && destination!=null)
- myMap.put(new Pair<>(name.toLowerCase(Locale.ROOT).trim(),destination.toLowerCase(Locale.ROOT).trim()), i);
+ myMap.put(new Pair<>(name.toLowerCase(Locale.ROOT).trim(),destination.toLowerCase(Locale.ROOT).trim()), i);
}
return myMap;
}
diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/PalinaAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/PalinaAdapter.java
--- a/app/src/main/java/it/reyboz/bustorino/adapters/PalinaAdapter.java
+++ b/app/src/main/java/it/reyboz/bustorino/adapters/PalinaAdapter.java
@@ -264,7 +264,7 @@
void showRouteFullDirection(Route route);
/**
- * Show the line with all the stops in the app
+ * Show the line with all the stops in the line screen
* @param route partial line info
*/
void requestShowingRoute(Route route);
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java b/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java
@@ -344,6 +344,11 @@
return name.replace(" ","");
}
+ /**
+ * Create the line name in GTFS format (e.g., "gtt:10U") from a more human readable name ("10")
+ * @param route the route object
+ * @return the code for the line in GTFS format
+ */
public static String getGtfsRouteID(Route route){
String routeName = route.getName();
String cutName = routeName.replace("\\s", "");
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java
@@ -22,8 +22,8 @@
import androidx.annotation.NonNull;
import de.siegmar.fastcsv.reader.CloseableIterator;
-import de.siegmar.fastcsv.reader.NamedCsvReader;
-import de.siegmar.fastcsv.reader.NamedCsvRow;
+import de.siegmar.fastcsv.reader.CsvReader;
+import de.siegmar.fastcsv.reader.NamedCsvRecord;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.networkTools;
import it.reyboz.bustorino.data.gtfs.CsvTableInserter;
@@ -195,8 +195,8 @@
//System.out.println(Arrays.toString(elements));
//lineElements = readCsvLine(header);
- NamedCsvReader csvReader = NamedCsvReader.builder().build(reader);
- CloseableIterator<NamedCsvRow> iterator = csvReader.iterator();
+ CsvReader<NamedCsvRecord> csvReader = CsvReader.builder().ofNamedCsvRecord(reader);
+ CloseableIterator<NamedCsvRecord> iterator = csvReader.iterator();
final CsvTableInserter inserter = new CsvTableInserter(tableName,con);
@@ -225,7 +225,12 @@
int c = 0;
while (iterator.hasNext()){
- final Map<String,String> rowsMap = iterator.next().getFields();
+ //final Map<String,String> rowsMap = iterator.next().getFields();
+ final NamedCsvRecord record = iterator.next();
+ final Map<String,String> rowsMap = new HashMap<>();
+ for (String col: record.getHeader()){
+ rowsMap.put(col, record.getField(col));
+ }
if (c < 1){
Log.d(DEBUG_TAG, " in map:"+rowsMap);
c++;
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
@@ -19,11 +19,15 @@
import com.google.transit.realtime.GtfsRealtime.VehiclePosition
+/**
+ * General data class for the live position update
+ * Used in both the GTFS and MaTO services
+ */
data class LivePositionUpdate(
val tripID: String, //tripID WITHOUT THE "gtt:" prefix
val startTime: String?,
val startDate: String?,
- val routeID: String,
+ val routeID: String, // routeID DOES NOT HAVE THE "gtt:" PREFIX
val vehicle: String,
var latitude: Double,
@@ -53,23 +57,10 @@
position.timestamp,
null
)
- /*data class VehicleInfo(
- val id: String,
- val label:String
- )
-
- */
- /*fun withNewPositionAndBearing(latitude: Double, longitude: Double, bearing: Float) =
- LivePositionUpdate(this.tripID, this.startTime, this.startTime,
- this.routeID, this.vehicle, latitude, longitude, bearing,
- this.timestamp,this.nextStop)
- fun withNewPosition(latitude: Double, longitude: Double) =
- LivePositionUpdate(this.tripID, this.startTime, this.startTime,
- this.routeID, this.vehicle, latitude, longitude, this.bearing,
- this.timestamp,this.nextStop)
-
- */
+ fun getLineGTFSFormat(): String{
+ return "gtt:$routeID"
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/utils.java b/app/src/main/java/it/reyboz/bustorino/backend/utils.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/utils.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/utils.java
@@ -32,12 +32,8 @@
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
+import java.text.SimpleDateFormat;
+import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -390,4 +386,23 @@
return Html.fromHtml(text);
}
}
+ /**
+ * Convert an integer (long) timestamp into a String
+ * @param timestamp the timestamp in seconds (NOT milliseconds)
+ * @return the formatted String
+ */
+ public static String unixTimestampToLocalTime(long timestamp){
+ return unixTimestampToLocalTime(timestamp, "dd/MM/yyyy HH:mm:ss");
+ }
+ /**
+ * Convert an integer (long) timestamp into a String
+ * @param timestamp the timestamp in seconds (NOT milliseconds)
+ * @param patternFormat the format to convert it to
+ * @return the formatted String
+ */
+ public static String unixTimestampToLocalTime(long timestamp, String patternFormat) {
+ Date date = new Date(timestamp * 1000L); // seconds to milliseconds
+ SimpleDateFormat format = new SimpleDateFormat(patternFormat, Locale.getDefault());
+ return format.format(date);
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
--- a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
@@ -33,7 +33,7 @@
import androidx.annotation.Nullable;
import de.siegmar.fastcsv.reader.CloseableIterator;
import de.siegmar.fastcsv.reader.CsvReader;
-import de.siegmar.fastcsv.reader.CsvRow;
+import de.siegmar.fastcsv.reader.CsvRecord;
import de.siegmar.fastcsv.writer.CsvWriter;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.StopsDBInterface;
@@ -342,27 +342,27 @@
Cursor cursor = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray,null,null,null,null, sortOrder);
final int nCols = 2;//cursor.getColumnCount();
- writer.writeRow(cursor.getColumnNames());
+ writer.writeRecord(cursor.getColumnNames());
while (cursor.moveToNext()){
String[] arr = {cursor.getString(0), cursor.getString(1)};
- writer.writeRow(arr);
+ writer.writeRecord(arr);
}
cursor.close();
return true;
}
- public int insertRowsFromCSV(CsvReader reader){
+ public int insertRowsFromCSV(CsvReader<CsvRecord> reader){
SQLiteDatabase db = this.getWritableDatabase();
boolean firstrow = true;
final HashMap<String,Integer> colIndexByRows = new HashMap<>();
- final CloseableIterator<CsvRow> rowsIter = reader.iterator();
+ final CloseableIterator<CsvRecord> rowsIter = reader.iterator();
if (!rowsIter.hasNext()){
//nothing to do, it's an empty file
return -1;
}
- final CsvRow firstRow = rowsIter.next();
+ final CsvRecord firstRow = rowsIter.next();
// close if there isn't another rows
if(!rowsIter.hasNext()) return -2;
for (int i =0; i<firstRow.getFieldCount(); i++){
@@ -378,7 +378,7 @@
final int col_id = colIndexByRows.get(COL_ID);
final int col_username = colIndexByRows.get(COL_USERNAME);
while (rowsIter.hasNext()){
- final CsvRow row = rowsIter.next();
+ final CsvRecord row = rowsIter.next();
final ContentValues cv = new ContentValues();
cv.put(COL_ID, row.getField(col_id));
cv.put(COL_USERNAME, row.getField(col_username));
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt
@@ -122,11 +122,11 @@
DEBUG_TAG, """Need to show line for route: gtfsID ${route.gtfsId} name ${route.name}"""
)
if (route.gtfsId != null) {
- mListener.showLineOnMap(route.gtfsId, stopID)
+ mListener.openLineFromStop(route.gtfsId, stopID)
} else {
val gtfsID = FiveTNormalizer.getGtfsRouteID(route)
Log.d(DEBUG_TAG, "GtfsID for route is: $gtfsID")
- mListener.showLineOnMap(gtfsID, stopID)
+ mListener.openLineFromStop(gtfsID, stopID)
}
}
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/BackupImportFragment.kt
@@ -50,9 +50,7 @@
Toast.makeText(context, R.string.message_check_at_least_one, Toast.LENGTH_SHORT).show()
}
else if (result.resultCode == Activity.RESULT_OK) {
-
result.data?.data?.also { uri ->
-
loadZipData(uri,loadFavorites, loadPreferences)
}
}
@@ -198,7 +196,7 @@
FAVORITES_NAME -> if (loadFavorites) {
val reader = InputStreamReader(zipstream)
- val csvReader = CsvReader.builder().build(reader)
+ val csvReader = CsvReader.builder().ofCsvRecord(reader)
val userDB = UserDB(context)
val updated = userDB.insertRowsFromCSV(csvReader)
@@ -258,7 +256,7 @@
val contentResolver = context.contentResolver
contentResolver.openInputStream(uri)?.use {
InputStreamReader(it).use { stream ->
- val csvReader = CsvReader.builder().build(stream)
+ val csvReader = CsvReader.builder().ofCsvRecord(stream)
val userDB = UserDB(context)
val updated = userDB.insertRowsFromCSV(csvReader)
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -1,5 +1,6 @@
package it.reyboz.bustorino.fragments;
+import android.os.Bundle;
import androidx.annotation.Nullable;
import it.reyboz.bustorino.backend.Stop;
@@ -37,8 +38,16 @@
void showMapCenteredOnStop(Stop stop);
/**
- * We want to show the line in detail for route
+ * We want to show the line in detail for route coming from a stop
* @param routeGtfsId the route gtfsID (eg, "gtt:10U")
*/
- void showLineOnMap(String routeGtfsId,@Nullable String fromStopID);
+ void openLineFromStop(String routeGtfsId, @Nullable String fromStopID);
+
+ /**
+ * Open the line screen on the line, from a live vehicle (optional pattern)
+ * @param routeGtfsId the route gtfsID (eg, "gtt:10U")
+ * @param optionalPatternId the pattern name (can be null)
+ * @param args extra arguments given as Bundle
+ */
+ void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args);
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt
@@ -15,27 +15,36 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
+import android.widget.ImageButton
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
+import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.gson.JsonObject
import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.FiveTNormalizer
import it.reyboz.bustorino.backend.LivePositionTripPattern
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.backend.utils
import it.reyboz.bustorino.data.PreferencesHolder
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import it.reyboz.bustorino.map.MapLibreUtils
import it.reyboz.bustorino.util.ViewUtils
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
+import it.reyboz.bustorino.viewmodels.MapStateViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.maplibre.android.MapLibre
-import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.location.LocationComponent
import org.maplibre.android.location.LocationComponentOptions
@@ -61,7 +70,7 @@
abstract class GeneralMapLibreFragment: ScreenBaseFragment(), OnMapReadyCallback {
protected var map: MapLibreMap? = null
protected var shownStopInBottomSheet : Stop? = null
- protected var savedMapStateOnPause : Bundle? = null
+ //protected var savedMapStateOnPause : Bundle? = null
protected var fragmentListener: CommonFragmentListener? = null
@@ -95,11 +104,13 @@
protected lateinit var stopTitleTextView: TextView
protected lateinit var stopNumberTextView: TextView
protected lateinit var linesPassingTextView: TextView
+ protected lateinit var extraBottomTextView: TextView
protected lateinit var arrivalsCard: CardView
protected lateinit var directionsCard: CardView
protected lateinit var bottomrightImage: ImageView
-
protected lateinit var locationComponent: LocationComponent
+ protected lateinit var busPositionsIconButton: ImageButton
+
protected var lastLocation : Location? = null
@@ -115,9 +126,13 @@
//extra items to use the LibreMap
- protected lateinit var symbolManager : SymbolManager
+ protected var symbolManager : SymbolManager? = null
protected var stopActiveSymbol: Symbol? = null
protected var stopsLayerStarted = false
+ protected val livePositionsViewModel : LivePositionsViewModel by activityViewModels()
+
+ //private lateinit var symbolManager: SymbolManager
+ protected val mapStateViewModel: MapStateViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -150,6 +165,7 @@
directionsCard = view.findViewById(R.id.directionsCardButton)
bottomrightImage = view.findViewById(R.id.rightmostImageView)
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
+ extraBottomTextView = view.findViewById(R.id.extraBottomTextView)
}
override fun onResume() {
@@ -211,6 +227,7 @@
} else throw RuntimeException("$context must implement CommonFragmentListener")
}
+ /*
protected fun restoreMapStateFromBundle(bundle: Bundle): Boolean{
val nullDouble = -10_000.0
var boundsRestored =false
@@ -266,6 +283,8 @@
return b
}
+ */
+
protected fun stopToGeoJsonFeature(s: Stop): Feature{
return Feature.fromGeometry(
Point.fromLngLat(s.longitude!!, s.latitude!!),
@@ -299,15 +318,15 @@
}
}
if (vehShowing==v){
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
}
}
}
// Hide the bottom sheet and remove extra symbol
- protected fun hideStopBottomSheet(){
+ protected fun hideStopOrBusBottomSheet(){
if (stopActiveSymbol!=null){
- symbolManager.delete(stopActiveSymbol)
+ symbolManager?.delete(stopActiveSymbol)
stopActiveSymbol = null
}
if(!showOpenStopWithSymbolLayer()){
@@ -323,22 +342,35 @@
vehShowing = ""
updatePositionsIcons(true)
}
+ extraBottomTextView.visibility = View.GONE
}
protected fun initSymbolManager(mapReady: MapLibreMap , style: Style){
- symbolManager = SymbolManager(mapView,mapReady,style)
- symbolManager.iconAllowOverlap = true
- symbolManager.textAllowOverlap = false
-
- symbolManager.addClickListener{ _ ->
- if (stopActiveSymbol!=null){
- hideStopBottomSheet()
-
+ val sm = SymbolManager(mapView, mapReady, style)
+ sm.iconAllowOverlap = true
+ sm.textAllowOverlap = false
+ sm.addClickListener { _ ->
+ if (stopActiveSymbol != null) {
+ hideStopOrBusBottomSheet()
return@addClickListener true
} else
return@addClickListener false
}
+ symbolManager = sm
+ }
+
+ /**
+ * Change the icon indicating the status of the live Positions
+ */
+ protected fun setBusPositionsIcon(enabled: Boolean, error: Boolean){
+ val ctx = requireContext()
+ if(!enabled)
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_inactive))
+ else if(error)
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_notworking))
+ else
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_active))
}
@@ -493,6 +525,53 @@
updatePositionsIcons(false)
}
+ /**
+ * Shared bottom sheet setup. The [onDirectionsClick] lambda is called when
+ * directionsCard is tapped; it receives the pattern code (empty string when
+ * no pattern is available) so each subclass can navigate as it sees fit.
+ */
+ protected fun showVehicleTripInBottomSheet(
+ veh: String,
+ onDirectionsClick: (patternCode: String) -> Unit
+ ) {
+ val data = updatesByVehDict[veh] ?: run {
+ Log.w(DEBUG_TAG, "Asked to show vehicle $veh, but it's not present in the updates")
+ return
+ }
+ bottomLayout?.let {
+ val lineName = FiveTNormalizer.fixShortNameForDisplay(
+ GtfsUtils.getLineNameFromGtfsID(data.posUpdate.routeID), true
+ )
+ val pat = data.pattern
+ if (pat != null) {
+ stopTitleTextView.text = pat.headsign
+ stopTitleTextView.visibility = View.VISIBLE
+ stopNumberTextView.text = getString(R.string.line_fill_towards, lineName)
+ } else {
+ stopTitleTextView.visibility = View.GONE
+ stopNumberTextView.text = getString(R.string.line_fill, lineName)
+ }
+ directionsCard.setOnClickListener {
+ onDirectionsClick(pat?.code ?: "")
+ }
+ directionsCard.visibility = View.VISIBLE
+ bottomrightImage.setImageDrawable(
+ ResourcesCompat.getDrawable(resources, R.drawable.ic_magnifying_glass, activity?.theme)
+ )
+ val colorBlue = ResourcesCompat.getColor(resources, R.color.blue_500, activity?.theme)
+ ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorBlue))
+ linesPassingTextView.text = getString(R.string.vehicle_fill, data.posUpdate.vehicle)
+ arrivalsCard.visibility = View.GONE
+
+ extraBottomTextView.text = getString(R.string.updated_fill, utils.unixTimestampToLocalTime(data.posUpdate.timestamp))
+ extraBottomTextView.visibility = View.VISIBLE
+ }
+ vehShowing = veh
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+ updatePositionsIcons(true)
+ Log.d(DEBUG_TAG, "Shown vehicle $veh in bottom sheet")
+ }
+
/**
* Update the bus positions displayed on the map, from the existing data
*
@@ -644,14 +723,13 @@
Log.d(DEBUG_TAG, "Showing stop: ${stop.ID}")
if (showOpenStopWithSymbolLayer()) {
- stopActiveSymbol = symbolManager.create(
+ stopActiveSymbol = symbolManager?.create(
SymbolOptions()
.withLatLng(LatLng(stop.latitude!!, stop.longitude!!))
.withIconImage(STOP_ACTIVE_IMG)
.withIconAnchor(ICON_ANCHOR_CENTER)
-
)
- } else{
+ } else {
val list = ArrayList<Feature>()
list.add(stopToGeoJsonFeature(stop))
selectedStopSource.setGeoJson(
@@ -769,15 +847,26 @@
}
style.addLayerAbove(busesLayer, STOPS_LAYER_ID)
- val selectedBusLayer = SymbolLayer(SEL_BUS_LAYER, SEL_BUS_SOURCE).withProperties(
+ val selectedBusLayer = SymbolLayer(SEL_BUS_LAYER, SEL_BUS_SOURCE).apply {
+ withProperties(
PropertyFactory.iconImage(BUS_SEL_IMAGE_ID),
PropertyFactory.iconSize(busIconsScale),
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconIgnorePlacement(true),
PropertyFactory.iconRotate(Expression.get("bearing")),
PropertyFactory.iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP)
+ )
+ if (withLabels){
+ withProperties(PropertyFactory.textAnchor(TEXT_ANCHOR_CENTER),
+ PropertyFactory.textAllowOverlap(true),
+ PropertyFactory.textField(Expression.get("line")),
+ PropertyFactory.textColor(Color.WHITE),
+ PropertyFactory.textRotationAlignment(TEXT_ROTATION_ALIGNMENT_VIEWPORT),
+ PropertyFactory.textSize(12f),
+ PropertyFactory.textFont(arrayOf("noto_sans_regular")))
+ }
+ }
- )
style.addLayerAbove(selectedBusLayer, BUSES_LAYER_ID)
}
@@ -791,6 +880,35 @@
return locManager.allProviders.contains(LocationManager.GPS_PROVIDER)
}
+ /**
+ * Update automatically the icon when the live position service changes status
+ */
+ protected fun observeStatusLivePositions(){
+ livePositionsViewModel.serviceStatus.observe(viewLifecycleOwner){ status ->
+ //if service is active, update the bus positions icon
+ when(status) {
+ LivePositionsServiceStatus.OK ->
+ setBusPositionsIcon(true, error = false)
+
+ LivePositionsServiceStatus.NO_POSITIONS -> setBusPositionsIcon(true, error = true)
+
+ else -> setBusPositionsIcon( true, error = true)
+ }
+ }
+ }
+
+ /**
+ * Clear all buses from the map
+ */
+ protected fun clearAllBusPositionsInMap(){
+ for ((k, anim) in animatorsByVeh){
+ anim.cancel()
+ }
+ animatorsByVeh.clear()
+ updatesByVehDict.clear()
+ updatePositionsIcons(forced = false)
+ }
+
companion object{
private const val DEBUG_TAG="GeneralMapLibreFragment"
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -36,7 +36,6 @@
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
-import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
@@ -59,7 +58,7 @@
import it.reyboz.bustorino.middleware.LocationUtils
import it.reyboz.bustorino.util.Permissions
import it.reyboz.bustorino.viewmodels.LinesViewModel
-import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
+import it.reyboz.bustorino.viewmodels.MapStateViewModel
import kotlinx.coroutines.Runnable
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
@@ -102,7 +101,6 @@
private var patternShown: MatoPatternWithStops? = null
private val viewModel: LinesViewModel by viewModels()
- private val mapViewModel: MapViewModel by viewModels()
private var firstInit = true
private var pausedFragment = false
private lateinit var switchButton: ImageButton
@@ -136,7 +134,8 @@
private lateinit var stopsRecyclerView: RecyclerView
private lateinit var descripTextView: TextView
- private var stopIDFromToShow: String? = null
+ private var stopIDFromToShow = ""
+ private var patternIdToShow = ""
//adapter for recyclerView
private val stopAdapterListener= object : StopAdapterListener {
override fun onTappedStop(stop: Stop?) {
@@ -205,7 +204,8 @@
private var showOnTopOfLine = false
private var recyclerInitDone = false
- private var useMQTTPositions = true
+ private var usingMQTTPositions = true
+ private var restoredCameraInMap = false
@@ -213,15 +213,14 @@
private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
- private val liveBusViewModel: LivePositionsViewModel by activityViewModels()
-
//extra items to use the LibreMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = requireArguments()
lineID = args.getString(LINEID_KEY,"")
- stopIDFromToShow = args.getString(STOPID_FROM_KEY)
+ stopIDFromToShow = args.getString(STOPID_FROM_KEY, "") //can be null
+ patternIdToShow = args.getString(PATTERN_SHOW_KEY, "")
}
@SuppressLint("SetTextI18n")
@@ -239,9 +238,15 @@
//lineID = requireArguments().getString(LINEID_KEY, "")
arguments?.let {
lineID = it.getString(LINEID_KEY, "")
+ stopIDFromToShow = it.getString(STOPID_FROM_KEY, "") //can be null
+ patternIdToShow = it.getString(PATTERN_SHOW_KEY, "")
+ Log.d(DEBUG_TAG, "LineID selected: $lineID, stopIDFromToShow: $stopIDFromToShow, patternIdToShow: $patternIdToShow")
}
+
switchButton = rootView.findViewById(R.id.switchImageButton)
locationIcon = rootView.findViewById(R.id.locationEnableIcon)
+ busPositionsIconButton = rootView.findViewById(R.id.busPositionsImageButton)
+
favoritesButton = rootView.findViewById(R.id.favoritesButton)
stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
descripTextView = rootView.findViewById(R.id.lineDescripTextView)
@@ -254,7 +259,7 @@
// Setup close button
rootView.findViewById<View>(R.id.btnClose).setOnClickListener {
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
}
val titleTextView = rootView.findViewById<TextView>(R.id.titleTextView)
@@ -295,13 +300,46 @@
//set click Listener
view.setOnClickListener(this::onPositionIconButtonClick)
}
+ busPositionsIconButton.setOnClickListener {
+ LivePositionsDialogFragment().show(parentFragmentManager, "LivePositionsDialog")
+ }
//set
//INITIALIZE VIEW MODELS
viewModel.setRouteIDQuery(lineID)
- liveBusViewModel.setGtfsLineToFilterPos(lineID, null)
+ livePositionsViewModel.setGtfsLineToFilterPos(lineID, null)
+ //observe the change, clear buses when switching position
+ livePositionsViewModel.useMQTTPositionsLiveData.observe(viewLifecycleOwner){ useMQTT->
+ //Log.d(DEBUG_TAG, "Changed MQTT positions, now have to use MQTT: $useMQTT")
+ if (isResumed) {
+ //Log.d(DEBUG_TAG, "Deciding to switch, the current source is using MQTT: $usingMQTTPositions")
+ if(useMQTT!=usingMQTTPositions){
+ // we have to switch
+ val clearPos = PreferenceManager.getDefaultSharedPreferences(requireContext()).getBoolean("positions_clear_on_switch_pref", true)
+ livePositionsViewModel.clearOldPositionsUpdates()
+ if(useMQTT){
+ //switching to MQTT, the GTFS positions are disabled automatically
+ livePositionsViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+ } else{
+ //switching to GTFS RT: stop Mato, launch first request
+ livePositionsViewModel.stopMatoUpdates()
+ livePositionsViewModel.requestGTFSUpdates()
+ }
+ Log.d(DEBUG_TAG, "Should clear positions: $clearPos")
+ if (clearPos) {
+ livePositionsViewModel.clearAllPositions()
+ //force clear of the viewed data
+ if(vehShowing.isNotEmpty()) hideStopOrBusBottomSheet()
+ clearAllBusPositionsInMap()
+ }
+
+ }
+ }
+ usingMQTTPositions = useMQTT
+
+ }
val keySourcePositions = getString(R.string.pref_positions_source)
- useMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ usingMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString(keySourcePositions, "mqtt").contentEquals("mqtt")
viewModel.patternsWithStopsByRouteLiveData.observe(viewLifecycleOwner){
@@ -313,6 +351,8 @@
if(mapView.visibility ==View.VISIBLE)
patternShown?.let{
// We have the pattern and the stops here, time to display them
+ //TODO: Decide if we should follow the camera view given by the previous screen (probably the map fragment)
+ // use !restoredCameraInMap to do so
displayPatternWithStopsOnMap(it,stops, true)
} ?:{
Log.w(DEBUG_TAG, "The viewingPattern is null!")
@@ -353,11 +393,11 @@
stopAnimations()
updatesByVehDict.clear()
updatePositionsIcons(true)
- liveBusViewModel.retriggerPositionUpdate()
+ livePositionsViewModel.retriggerPositionUpdate()
}
}
}
- liveBusViewModel.setGtfsLineToFilterPos(lineID, patternWithStops.pattern)
+ livePositionsViewModel.setGtfsLineToFilterPos(lineID, patternWithStops.pattern)
}
@@ -366,6 +406,8 @@
}
Log.d(DEBUG_TAG, "Views created!")
+ observeStatusLivePositions()
+
return rootView
}
@@ -375,14 +417,15 @@
mapView.visibility = View.GONE
stopsRecyclerView.visibility = View.VISIBLE
locationIcon?.visibility = View.GONE
+ busPositionsIconButton?.visibility = View.GONE
viewModel.setMapShowing(false)
- if(useMQTTPositions) liveBusViewModel.stopMatoUpdates()
+ if(usingMQTTPositions) livePositionsViewModel.stopMatoUpdates()
//map.overlayManager.remove(busPositionsOverlay)
switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_white_30))
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
if(locationComponent.isLocationComponentEnabled){
locationComponent.isLocationComponentEnabled = false
@@ -395,14 +438,16 @@
stopsRecyclerView.visibility = View.GONE
mapView.visibility = View.VISIBLE
locationIcon?.visibility = View.VISIBLE
+ busPositionsIconButton.visibility = View.VISIBLE
+
viewModel.setMapShowing(true)
//map.overlayManager.add(busPositionsOverlay)
//map.
- if(useMQTTPositions)
- liveBusViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+ if(usingMQTTPositions)
+ livePositionsViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
else
- liveBusViewModel.requestGTFSUpdates()
+ livePositionsViewModel.requestGTFSUpdates()
switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_list_30))
@@ -469,6 +514,7 @@
*/
override fun onMapReady(mapReady: MapLibreMap) {
this.map = mapReady
+ var setViewAlready = false
val context = requireContext()
val mjson = MapLibreStyles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json")
@@ -491,11 +537,6 @@
setupBusLayer(style)
initSymbolManager(mapReady, style)
-
- mapViewModel.stopShowing?.let {
- openStopInBottomSheet(it)
- }
- mapViewModel.stopShowing = null
toRunWhenMapReady?.run()
toRunWhenMapReady = null
mapInitialized.set(true)
@@ -504,9 +545,33 @@
viewModel.stopsForPatternLiveData.value?.let {
Log.d(DEBUG_TAG, "Show stops from the cache")
displayPatternWithStopsOnMap(patternShown!!, it, true)
+ //Show stop from cache
+ mapStateViewModel.lastOpenStopID.value?.let{ sID->
+ val s= it.filter { stop -> stop.ID==sID }
+ if (s.isEmpty()) {
+ if(sID.isNotEmpty())
+ Log.w(DEBUG_TAG,"Wanted to open stop $sID in map but it was not loaded!")
+ }
+ else openStopInBottomSheet(s[0])
+
+ }
}
+
+
}
+ var restoredMapState = mapStateViewModel.restoreMapState(mapReady)
+ arguments?.let { args ->
+ // if there is a Camera State in the arguments, set it for the new camera (doesn't work yet!)
+ if (!restoredMapState && MapCameraState.checkInBundle(args)) {
+ val initCamState = MapCameraState.fromBundle(args)
+ //map?.let{
+ MapStateViewModel.restoreMapState(mapReady, initCamState)
+ setViewAlready = true
+ restoredMapState = true
+ }
+ }
+ restoredCameraInMap = restoredMapState
}
mapReady.addOnMapClickListener { point ->
@@ -521,7 +586,7 @@
val stop = viewModel.getStopByID(id)
stop?.let {
if (isBottomSheetShowing() || vehShowing.isNotEmpty()){
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
}
openStopInBottomSheet(it)
@@ -535,7 +600,7 @@
val vehid = feature.getStringProperty("veh")
val route = feature.getStringProperty("line")
if(isBottomSheetShowing())
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
//if(context!=null){
// Toast.makeText(context, "Veh $vehid on route ${route.slice(0..route.length-2)}", Toast.LENGTH_SHORT).show()
//}
@@ -556,18 +621,11 @@
// we start requesting the bus positions now
observeBusPositionUpdates()
- }
- /*savedMapStateOnPause?.let{
- restoreMapStateFromBundle(it)
- pendingLocationActivation = false
- Log.d(DEBUG_TAG, "Restored map state from the saved bundle")
}
- */
-
val zoom = 12.0
val latlngTarget = LatLng(MapLibreFragment.DEFAULT_CENTER_LAT, MapLibreFragment.DEFAULT_CENTER_LON)
-
+ if(!setViewAlready)
mapReady.cameraPosition = savedCameraPosition ?:CameraPosition.Builder().target(latlngTarget).zoom(zoom).build()
savedCameraPosition = null
@@ -580,8 +638,10 @@
}
private fun observeBusPositionUpdates(){
+
+
//live bus positions
- liveBusViewModel.filteredLocationUpdates.observe(viewLifecycleOwner){ pair ->
+ livePositionsViewModel.filteredLocationUpdates.observe(viewLifecycleOwner){ pair ->
//Log.d(DEBUG_TAG, "Received ${updates.size} updates for the positions")
val updates = pair.first
val vehiclesNotOnCorrectDir = pair.second
@@ -596,13 +656,13 @@
showVehicleTripInBottomSheet(veh)
}
//if not using MQTT positions
- if(!useMQTTPositions){
- liveBusViewModel.requestDelayedGTFSUpdates(2000)
+ if(!usingMQTTPositions){
+ livePositionsViewModel.requestDelayedGTFSUpdates(2000)
}
}
//download missing tripIDs
- liveBusViewModel.tripsGtfsIDsToQuery.observe(viewLifecycleOwner){
+ livePositionsViewModel.tripsGtfsIDsToQuery.observe(viewLifecycleOwner){
//gtfsPosViewModel.downloadTripsFromMato(dat);
MatoTripsDownloadWorker.requestMatoTripsDownload(
it, requireContext().applicationContext,
@@ -611,63 +671,18 @@
}
}
-
- private fun showVehicleTripInBottomSheet(veh: String){
- val data = updatesByVehDict[veh]
- if(data==null) {
- Log.w(DEBUG_TAG,"Asked to show vehicle $veh, but it's not present in the updates")
- return
- }
-
- bottomLayout?.let {
- val lineName = FiveTNormalizer.fixShortNameForDisplay(
- GtfsUtils.getLineNameFromGtfsID(data.posUpdate.routeID), true)
- val pat = data.pattern
- if (pat!=null){
- //WE HAVE THE DIRECTIONS DATA
- stopTitleTextView.text = pat.headsign
- stopTitleTextView.visibility = View.VISIBLE
- Log.d(DEBUG_TAG, "Showing headsign ${pat.headsign} for vehicle $veh")
- stopNumberTextView.text = requireContext().getString(R.string.line_fill_towards, lineName)
-
- bottomrightImage.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_magnifying_glass, activity?.theme))
- directionsCard.setOnClickListener {
- data.pattern?.let {
-
- if(patternShown?.pattern?.code == it.code){
- context?.let { c->Toast.makeText(c, R.string.showing_same_direction, Toast.LENGTH_SHORT).show() }
- }else
- showPatternWithCode(it.code)
- } //TODO
- // ?: {
- // context?.let { ctx -> Toast.makeText(ctx,"") }
- //}
- }
- //set color
- val colorBlue = ResourcesCompat.getColor(resources,R.color.blue_500,activity?.theme)
- ViewCompat.setBackgroundTintList(directionsCard, ColorStateList.valueOf(colorBlue))
- directionsCard.visibility = View.VISIBLE
+ private fun showVehicleTripInBottomSheet(veh: String) {
+ super.showVehicleTripInBottomSheet(veh) { patternCode ->
+ //this is checked in @GeneralMapLibreFragment
+ //val data = updatesByVehDict[veh] ?: return@showVehicleTripInBottomSheet
+ if (patternCode.isEmpty()) return@showVehicleTripInBottomSheet
+ if (patternShown?.pattern?.code == patternCode) {
+ Toast.makeText(context, R.string.showing_same_direction, Toast.LENGTH_SHORT).show()
} else {
- //stopTitleTextView.text = "NN"
- stopTitleTextView.visibility = View.GONE
- stopNumberTextView.text = requireContext().getString(R.string.line_fill, lineName)
- directionsCard.visibility = View.GONE
-
+ showPatternWithCode(patternCode)
}
- linesPassingTextView.text = requireContext().getString(R.string.vehicle_fill, data.posUpdate.vehicle)
}
-
- arrivalsCard.visibility=View.GONE
-
- vehShowing = veh
- bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
-
- //call update position to color the bus special
- updatePositionsIcons(true)
- //isBottomSheetShowing = true
- Log.d(DEBUG_TAG, "Shown vehicle $veh in bottom layout")
}
-
// ------- MAP LAYERS INITIALIZE ----
/**
* Initialize the map layers for the stops
@@ -730,29 +745,29 @@
initStopsLayer(style, null, POLY_ARROWS_LAYER)
}
+ private fun filterPatternFromArgs(patterns: List<MatoPatternWithStops>): MatoPatternWithStops?{
+ var p: MatoPatternWithStops? = null
- /**
- * Save the loaded pattern data, without the stops!
- */
- private fun savePatternsToShow(patterns: List<MatoPatternWithStops>){
-
- currentPatterns = patterns.sortedWith(patternsSorter)
-
- patternsAdapter?.let {
- it.clear()
- it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
- it.notifyDataSetChanged()
+ if (patternIdToShow.isNotEmpty()){
+ for (patt in currentPatterns) {
+ if (patt.pattern.code == patternIdToShow){
+ p = patt
+ }
+ }
+ if(p==null)
+ Log.w(DEBUG_TAG, "We had to show the pattern with code $patternIdToShow, but we didn't find it")
+ else
+ Log.d(DEBUG_TAG, "Requesting to show pattern with code $patternIdToShow, found pattern ${p.pattern.code}")
}
// if we are loading from a stop, find it
- val patternToShow = stopIDFromToShow?.let { sID ->
- val stopGtfsID = "gtt:$sID"
- var p: MatoPatternWithStops? = null
+ else if(stopIDFromToShow.isNotEmpty()) {
+ val stopGtfsID = "gtt:$stopIDFromToShow"
var pLength = 0
- for(patt in currentPatterns){
- for(pstop in patt.stopsIndices){
- if(pstop.stopGtfsId == stopGtfsID){
+ for (patt in currentPatterns) {
+ for (pstop in patt.stopsIndices) {
+ if (pstop.stopGtfsId == stopGtfsID) {
//found
- if (patt.stopsIndices.size>pLength){
+ if (patt.stopsIndices.size > pLength) {
p = patt
pLength = patt.stopsIndices.size
}
@@ -761,20 +776,32 @@
}
}
}
- p
- }
- if(stopIDFromToShow!=null){
- if(patternToShow==null)
+ if(p==null)
Log.w(DEBUG_TAG, "We had to show the pattern from stop $stopIDFromToShow, but we didn't find it")
else
- Log.d(DEBUG_TAG, "Requesting to show pattern from stop $stopIDFromToShow, found pattern ${patternToShow.pattern.code}")
+ Log.d(DEBUG_TAG, "Requesting to show pattern from stop $stopIDFromToShow, found pattern ${p.pattern.code}")
}
- //unset the stopID to show
- if(patternToShow!=null) {
+ stopIDFromToShow = ""
+ patternIdToShow = ""
+ return p
+ }
+ /**
+ * Save the loaded pattern data, without the stops!
+ */
+ private fun savePatternsToShow(patterns: List<MatoPatternWithStops>){
+
+ currentPatterns = patterns.sortedWith(patternsSorter)
+
+ patternsAdapter?.let {
+ it.clear()
+ it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
+ it.notifyDataSetChanged()
+ }
+ val patternToShow = filterPatternFromArgs(patterns)
+ if(patternToShow!=null) {
//showPattern(patternToShow)
patternShown = patternToShow
- stopIDFromToShow = null
}
patternShown?.let {
showPattern(it)
@@ -812,6 +839,9 @@
//setPatternAndReqStops(patternWs)
}
+ /**
+ * Zoom on the map to get the pattern
+ */
private fun zoomToCurrentPattern(){
if(polyline==null) return
val NULL_VALUE = -4000.0
@@ -927,27 +957,12 @@
Log.e(DEBUG_TAG, "Stops layer is not started!!")
}
- /* OLD CODE
- for(s in stops){
- val gp =
- val marker = MarkerUtils.makeMarker(
- gp, s.ID, s.stopDefaultName,
- s.routesThatStopHereToString(),
- map,stopTouchResponder, stopIcon,
- R.layout.linedetail_stop_infowindow,
- R.color.line_drawn_poly
- )
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
- stopsOverlay.add(marker)
- }
- */
//POINTS LIST IS NOT IN ORDER ANY MORE
//if(!map.overlayManager.contains(stopsOverlay)){
// map.overlayManager.add(stopsOverlay)
//}
if(zoomToPattern) zoomToCurrentPattern()
- //map.invalidate()
}
private fun initializeRecyclerView(){
@@ -999,31 +1014,16 @@
pausedFragment = false
val keySourcePositions = getString(R.string.pref_positions_source)
- useMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ usingMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString(keySourcePositions, "mqtt").contentEquals("mqtt")
//separate paths
- if(useMQTTPositions)
- liveBusViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+ if(usingMQTTPositions)
+ livePositionsViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
else
- liveBusViewModel.requestGTFSUpdates()
-
-
- if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
- Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
- " zoom ${mapViewModel.currentZoom.value}")
- //THIS WAS A FIX FOR THE OLD OSMDROID MAP
- /*val controller = map.controller
- viewLifecycleOwner.lifecycleScope.launch {
- delay(100)
- Log.d(DEBUG_TAG, "zooming back to point")
- controller.animateTo(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!),
- mapViewModel.currentZoom.value!!,null,null)
- //controller.setCenter(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!))
- //controller.setZoom(mapViewModel.currentZoom.value!!)
- }
- */
- }
+ livePositionsViewModel.requestGTFSUpdates()
+
+
//initialize GUI here
fragmentListener?.readyGUIfor(FragmentKind.LINES)
@@ -1032,24 +1032,19 @@
override fun onPause() {
super.onPause()
mapView.onPause()
- if(useMQTTPositions) liveBusViewModel.stopMatoUpdates()
+ if(usingMQTTPositions) livePositionsViewModel.stopMatoUpdates()
pausedFragment = true
//save map
- val camera = map?.cameraPosition
- camera?.let {cam->
- mapViewModel.currentLat.value = cam.target?.latitude ?: -400.0
- mapViewModel.currentLong.value = cam.target?.longitude ?: -400.0
- mapViewModel.currentZoom.value = cam.zoom
+ map?.let{
+ //if map is initialized
+ mapStateViewModel.saveMapState(it)
}
-
+ mapStateViewModel.lastOpenStopID.postValue(shownStopInBottomSheet?.ID)
}
override fun onStop() {
super.onStop()
mapView.onStop()
- shownStopInBottomSheet?.let {
- mapViewModel.stopShowing = it
- }
shouldMapLocationBeReactivated = locationComponent.isLocationComponentEnabled
}
@@ -1093,6 +1088,7 @@
companion object {
private const val LINEID_KEY="lineID"
private const val STOPID_FROM_KEY="stopID"
+ private const val PATTERN_SHOW_KEY ="patternIDShow"
private const val DEBUG_TAG="BusTO-LineDetalFragment"
@@ -1103,6 +1099,14 @@
b.putString(STOPID_FROM_KEY, stopIDFrom)
return b
}
+
+ fun makeArgsPattern(lineID: String, patternShow: String?, extraArgs: Bundle?): Bundle {
+
+ val b= extraArgs ?: Bundle()
+ b.putString(LINEID_KEY, lineID)
+ b.putString(PATTERN_SHOW_KEY, patternShow)
+ return b
+ }
fun newInstance(lineID: String?, stopIDFrom: String?) = LinesDetailFragment().apply {
lineID?.let { arguments = makeArgs(it, stopIDFrom) }
}
@@ -1139,8 +1143,4 @@
private const val DEFAULT_CENTER_LAT = 45.12
private const val DEFAULT_CENTER_LON = 7.6858
}
-
- enum class BottomShowing{
- STOP, VEHICLE
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
@@ -65,7 +65,7 @@
}
private val routeClickListener = RouteAdapter.ItemClicker {
- fragmentListener.showLineOnMap(it.gtfsId, null)
+ fragmentListener.openLineFromStop(it.gtfsId, null)
}
private val arrows = HashMap<String, ImageView>()
private val durations = HashMap<String, Long>()
@@ -158,7 +158,7 @@
//create new item click listener every time
val adapter = RouteOnlyLineAdapter(routesNames){ pos, _ ->
val r = routes[pos]
- fragmentListener.showLineOnMap(r.gtfsId, null)
+ fragmentListener.openLineFromStop(r.gtfsId, null)
}
favoritesRecyclerView.adapter = adapter
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -725,9 +725,14 @@
}
@Override
- public void showLineOnMap(String routeGtfsId, @Nullable String stopIDFrom) {
+ public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom) {
//pass to activity
- mListener.showLineOnMap(routeGtfsId, stopIDFrom);
+ mListener.openLineFromStop(routeGtfsId, stopIDFrom);
+ }
+
+ @Override
+ public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) {
+ mListener.openLineFromVehicle(routeGtfsId, optionalPatternId, args);
}
@Override
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
@@ -4,6 +4,7 @@
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
+import android.content.res.ColorStateList
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
@@ -15,16 +16,19 @@
import android.widget.ImageButton
import android.widget.RelativeLayout
import android.widget.Toast
+import it.reyboz.bustorino.backend.FiveTNormalizer
+import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
+import androidx.room.concurrent.AtomicBoolean
import com.google.android.material.bottomsheet.BottomSheetBehavior
import it.reyboz.bustorino.R
-import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
@@ -32,7 +36,6 @@
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import it.reyboz.bustorino.map.MapLibreStyles
import it.reyboz.bustorino.util.Permissions
-import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
import it.reyboz.bustorino.viewmodels.StopsMapViewModel
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
@@ -60,8 +63,6 @@
private val stopsViewModel: StopsMapViewModel by viewModels()
private var stopsShowing = ArrayList<Stop>(0)
- //private lateinit var symbolManager: SymbolManager
-
// Sources for stops and buses are in GeneralMapLibreFragment
private var isUserMovingCamera = false
@@ -78,10 +79,15 @@
private lateinit var showUserPositionButton: ImageButton
private lateinit var centerUserButton: ImageButton
private lateinit var followUserButton: ImageButton
+
private var followingUserLocation = false
private var pendingLocationActivation = false
private var ignoreCameraMovementForFollowing = true
private var enablingPositionFromClick = false
+ private var restoredMapCamera = AtomicBoolean()
+ private var permissionsGranted = false
+
+ //TODO: Rewrite this mess using LocationEngineProvider in MapLibre
private val positionRequestLauncher = registerForActivityResult<Array<String>, Map<String, Boolean>>(
ActivityResultContracts.RequestMultiplePermissions(), ActivityResultCallback { result ->
if (result == null) {
@@ -92,9 +98,9 @@
} else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION]
&& java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) {
// We can use the position, restart location overlay
- Log.d(DEBUG_TAG, "HAVE THE PERMISSIONS")
+ permissionsGranted = true
if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null)
- return@ActivityResultCallback ///@registerForActivityResult
+ return@ActivityResultCallback
val locationManager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
var lastLoc = stopsViewModel.lastUserLocation
@SuppressLint("MissingPermission")
@@ -107,6 +113,7 @@
}
else if (lastLoc != null) {
+
if(LatLng(lastLoc.latitude, lastLoc.longitude).distanceTo(DEFAULT_LATLNG) <= MAX_DIST_KM*1000){
Log.d(DEBUG_TAG, "Showing the user position")
setMapLocationEnabled(true, true, false)
@@ -138,10 +145,7 @@
//BUS POSITIONS
private var usingMQTTPositions = true // THIS IS INSIDE VIEW MODEL NOW
- private val livePositionsViewModel : LivePositionsViewModel by activityViewModels()
- private lateinit var busPositionsIconButton: ImageButton
- //private var busLabelSymbolsByVeh = HashMap<String,Symbol>()
private val symbolsToUpdate = ArrayList<Symbol>()
private var initialStopToShow : Stop? = null
@@ -178,13 +182,8 @@
// Init the MapView
mapView = rootView.findViewById(R.id.libreMapView)
- val restoreBundle = stopsViewModel.savedState
- if(restoreBundle!=null){
- mapView.onCreate(restoreBundle)
- } else mapView.onCreate(savedInstanceState)
- mapView.getMapAsync(this) //{ //map ->
- //map.setStyle("https://demotiles.maplibre.org/style.json") }
-
+ mapView.onCreate(savedInstanceState)
+ mapView.getMapAsync(this)
//init bottom sheet
val bottomSheet = rootView.findViewById<RelativeLayout>(R.id.bottom_sheet)
@@ -243,24 +242,16 @@
Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT)
.show()
}
+ // PERMISSIONS REQUESTED AFTER MAP SETUP
}
// Setup close button
rootView.findViewById<View>(R.id.btnClose).setOnClickListener {
- hideStopBottomSheet()
- }
- livePositionsViewModel.serviceStatus.observe(viewLifecycleOwner){ status ->
- //if service is active, update the bus positions icon
- when(status) {
- LivePositionsServiceStatus.OK ->
- setBusPositionsIcon(true, error = false)
-
- LivePositionsServiceStatus.NO_POSITIONS -> setBusPositionsIcon(true, error = true)
-
- else -> setBusPositionsIcon(true, error = true)
- }
+ hideStopOrBusBottomSheet()
}
+ observeStatusLivePositions()
+ //observe change in source of the live positions
livePositionsViewModel.useMQTTPositionsLiveData.observe(viewLifecycleOwner){ useMQTT->
//Log.d(DEBUG_TAG, "Changed MQTT positions, now have to use MQTT: $useMQTT")
if (showBusLayer && isResumed) {
@@ -281,6 +272,7 @@
if (clearPos) {
livePositionsViewModel.clearAllPositions()
//force clear of the viewed data
+ if(vehShowing.isNotEmpty()) hideStopOrBusBottomSheet()
clearAllBusPositionsInMap()
}
@@ -303,8 +295,6 @@
this.map = mapReady
val context = requireContext()
val mjson = MapLibreStyles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context))
- //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json")
-
val builder = Style.Builder().fromJson(mjson!!)
@@ -323,8 +313,6 @@
displayStops(stopsInCache)
if(showBusLayer) setupBusLayer(style, withLabels = true, busIconsScale = 1.2f)
- initSymbolManager(mapReady, style)
-
// Start observing data now that everything is set up
observeStops()
}
@@ -350,9 +338,6 @@
//the user is moving the map
isUserMovingCamera = true
}
- map?.let { setFollowingUser(it.locationComponent.cameraMode == CameraMode.TRACKING) }
- //setFollowingUser()
-
}
mapReady.addOnMapClickListener { point ->
@@ -364,28 +349,39 @@
observeBusPositionUpdates()
//Restoring data
- var boundsRestored = false
- pendingLocationActivation = true
- stopsViewModel.savedState?.let{
- boundsRestored = restoreMapStateFromBundle(it)
- //why are we disabling it?
- pendingLocationActivation = it.getBoolean(KEY_LOCATION_ENABLED,true)
- Log.d(DEBUG_TAG, "Restored map state from the saved bundle: ")
- }
- if(pendingLocationActivation)
- positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
-
- //reset saved State at the end
- if((!boundsRestored)) {
- //set initial position
- //center position
- val latlngTarget = initialStopToShow?.let {
- LatLng(it.latitude!!, it.longitude!!)
- } ?: LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
- mapReady.cameraPosition = CameraPosition.Builder().target(latlngTarget).zoom(DEFAULT_ZOOM).build()
+
+ if (initialStopToShow!=null){
+ val s = initialStopToShow!!
+ mapReady.cameraPosition = CameraPosition.Builder().target(
+ LatLng(s.latitude!!, s.longitude!!)
+ ).zoom(DEFAULT_ZOOM).build()
+ restoredMapCamera.set(true)
+ } else{
+ var boundsRestored = false
+ //restore the map state here
+ map?.let{
+ boundsRestored = mapStateViewModel.restoreMapState(it)
+ mapStateViewModel.lastOpenStopID.value?.let{ sID->
+ val s= stopsViewModel.getStopByID(sID)
+ if (s==null) {
+ if(sID.isNotEmpty())
+ Log.w(DEBUG_TAG,"Wanted to open stop $sID in map but it was not loaded!")
+ }
+ else{
+ openStopInBottomSheet(s) }
+ }
+
+ }
+ if(!boundsRestored){
+ mapReady.cameraPosition = CameraPosition.Builder().target(
+ LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
+ ).zoom(DEFAULT_ZOOM).build()
+ }
+ restoredMapCamera.set(boundsRestored)
}
- //reset saved state
- stopsViewModel.savedState = null
+
+ pendingLocationActivation = true
+ positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
}
private fun onMapClickReact(point: LatLng): Boolean{
@@ -405,7 +401,7 @@
val sameStopClicked = shownStopInBottomSheet?.let { newstop.ID==it.ID } ?: false
Log.d(DEBUG_TAG, "Hiding clicked stop: $sameStopClicked")
if (isBottomSheetShowing()) {
- hideStopBottomSheet()
+ hideStopOrBusBottomSheet()
}
if(!sameStopClicked){
openStopInBottomSheet(newstop)
@@ -424,9 +420,14 @@
} else if (busNearby.isNotEmpty()) {
val feature = busNearby[0]
val vehid = feature.getStringProperty("veh")
- val route = feature.getStringProperty("line")
-
- Toast.makeText(context, "Veh $vehid on route $route", Toast.LENGTH_SHORT).show()
+ if (isBottomSheetShowing()) hideStopOrBusBottomSheet()
+ showVehicleTripInBottomSheet(vehid)
+ //move camera to center on vehicle
+ updatesByVehDict[vehid]?.let { dat ->
+ mapReady.animateCamera(
+ CameraUpdateFactory.newLatLng(LatLng(dat.posUpdate.latitude, dat.posUpdate.longitude)), 750
+ )
+ }
return true
}
}
@@ -455,19 +456,12 @@
override fun onStart() {
super.onStart()
- //restore state from viewModel
- stopsViewModel.savedState?.let {
- restoreMapStateFromBundle(it)
- //reset state
- stopsViewModel.savedState = null
- }
}
override fun onResume() {
super.onResume()
//mapView.onResume() handled in GeneralMapLibreFragment
- //val keySourcePositions = getString(R.string.pref_positions_source)
if(showBusLayer) {
//first, clean up all the old positions
livePositionsViewModel.clearOldPositionsUpdates()
@@ -480,7 +474,7 @@
livePositionsViewModel.requestGTFSUpdates()
usingMQTTPositions = false
}
- //mapViewModel.testCascade();
+
livePositionsViewModel.isLastWorkResultGood.observe(this) { d: Boolean ->
Log.d(
DEBUG_TAG, "Last trip download result is $d"
@@ -493,8 +487,6 @@
}
fragmentListener?.readyGUIfor(FragmentKind.MAP)
- //restore saved state
- savedMapStateOnPause?.let { restoreMapStateFromBundle(it) }
}
override fun onPause() {
@@ -502,7 +494,11 @@
mapView.onPause()
Log.d(DEBUG_TAG, "Fragment paused")
- savedMapStateOnPause = saveMapStateInBundle()
+ map?.let{
+ //if map is initialized
+ mapStateViewModel.saveMapState(it)
+ }
+ mapStateViewModel.lastOpenStopID.postValue(shownStopInBottomSheet?.ID)
if (livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.stopMatoUpdates()
}
@@ -511,10 +507,11 @@
super.onStop()
mapView.onStop()
Log.d(DEBUG_TAG, "Fragment stopped!")
- stopsViewModel.savedState = Bundle().let {
+ /* stopsViewModel.savedState = Bundle().let {
mapView.onSaveInstanceState(it)
it
}
+ */
//save last location
map?.locationComponent?.lastKnownLocation?.let{
stopsViewModel.lastUserLocation = it
@@ -537,6 +534,17 @@
return mapView
}
+ private fun showVehicleTripInBottomSheet(veh: String) {
+ val data = updatesByVehDict[veh] ?: return
+ super.showVehicleTripInBottomSheet(veh) { patternCode ->
+ map?.let { mapStateViewModel.saveMapState(it) }
+ fragmentListener?.openLineFromVehicle(
+ data.posUpdate.getLineGTFSFormat(),
+ patternCode,
+ mapStateViewModel.savedCameraState?.toBundle()
+ )
+ }
+ }
private fun observeStops() {
// Observe stops
stopsViewModel.stopsToShow.observe(viewLifecycleOwner) { stops ->
@@ -606,7 +614,9 @@
DEBUG_TAG,
"Have " + data.size + " trip updates, has Map start finished: " + mapInitCompleted
)
- if (mapInitCompleted) updateBusPositionsInMap(data)
+ if (mapInitCompleted) updateBusPositionsInMap(data, hasVehicleTracking = true) { veh ->
+ showVehicleTripInBottomSheet(veh)
+ }
if (!isDetached && !livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.requestDelayedGTFSUpdates(
3000
)
@@ -614,30 +624,6 @@
}
- /*private fun createLabelForVehicle(positionUpdate: LivePositionUpdate){
- val symOpt = SymbolOptions()
- .withLatLng(LatLng(positionUpdate.latitude, positionUpdate.longitude))
- .withTextColor("#ffffff")
- .withTextField(positionUpdate.routeID.substringBeforeLast('U'))
- .withTextSize(13f)
- .withTextAnchor(TEXT_ANCHOR_CENTER)
- .withTextFont(arrayOf( "noto_sans_regular"))//"noto_sans_regular", "sans-serif")) //"noto_sans_regular"))
-
- val newSymbol = symbolManager.create(symOpt
- )
- Log.d(DEBUG_TAG, "Symbol for veh ${positionUpdate.vehicle}: $newSymbol")
- busLabelSymbolsByVeh[positionUpdate.vehicle] = newSymbol
- }
- private fun removeVehicleLabel(vehicle: String){
- busLabelSymbolsByVeh[vehicle]?.let {
- symbolManager.delete(it)
- busLabelSymbolsByVeh.remove(vehicle)
- }
- }
-
- */
-
-
// ------ LOCATION STUFF -----
@SuppressLint("MissingPermission")
private fun requestInitialUserLocation() {
@@ -672,19 +658,6 @@
}
- /**
- * Clear all buses from the map
- */
- private fun clearAllBusPositionsInMap(){
- for ((k, anim) in animatorsByVeh){
- anim.cancel()
- }
- animatorsByVeh.clear()
- updatesByVehDict.clear()
- updatePositionsIcons(forced = false)
- }
-
-
/**
* Handles logic of enabling the user location on the map
@@ -697,7 +670,7 @@
if (permissionOk) {
Log.d(DEBUG_TAG, "Permission OK, starting location component, assumed: $assumePermissions, fromClick: $fromClick")
locationComponent.isLocationComponentEnabled = true
- if (initialStopToShow==null) {
+ if (!restoredMapCamera.get()) {
locationComponent.cameraMode = CameraMode.TRACKING //CameraMode.TRACKING
setFollowingUser(true)
}
@@ -711,7 +684,6 @@
Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show()
}
Log.d(DEBUG_TAG, "Requesting permission to show user location")
- enablingPositionFromClick = fromClick
showUserPositionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS)
}
} else{
@@ -727,6 +699,7 @@
}
+
private fun setLocationIconEnabled(enabled: Boolean){
if (enabled)
showUserPositionButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.location_circlew_red))
@@ -735,20 +708,6 @@
}
- /**
- * Helper method for GUI
- */
- private fun setBusPositionsIcon(enabled: Boolean, error: Boolean){
- val ctx = requireContext()
- if(!enabled)
- busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_inactive))
- else if(error)
- busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_notworking))
- else
- busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_active))
-
- }
-
private fun updateFollowingIcon(enabled: Boolean){
if(enabled)
followUserButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.walk_circle_active))
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapCameraState.kt b/app/src/main/java/it/reyboz/bustorino/map/MapCameraState.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/MapCameraState.kt
@@ -0,0 +1,41 @@
+package it.reyboz.bustorino.map
+
+import android.os.Bundle
+
+
+data class MapCameraState(
+ val latitude: Double,
+ val longitude: Double,
+ val zoom: Double,
+ val bearing: Double,
+ val tilt: Double
+){
+ fun toBundle(): Bundle = Bundle().apply {
+ putDouble(KEY_LATITUDE, latitude)
+ putDouble(KEY_LONGITUDE, longitude)
+ putDouble(KEY_ZOOM, zoom)
+ putDouble(KEY_BEARING, bearing)
+ putDouble(KEY_TILT, tilt)
+ }
+
+ companion object {
+ private const val KEY_LATITUDE = "cam-latitude"
+ private const val KEY_LONGITUDE = "cam-longitude"
+ private const val KEY_ZOOM = "cam-zoom"
+ private const val KEY_BEARING = "cam-bearing"
+ private const val KEY_TILT = "cam-tilt"
+
+ fun fromBundle(bundle: Bundle): MapCameraState = MapCameraState(
+ latitude = bundle.getDouble(KEY_LATITUDE),
+ longitude = bundle.getDouble(KEY_LONGITUDE),
+ zoom = bundle.getDouble(KEY_ZOOM),
+ bearing = bundle.getDouble(KEY_BEARING),
+ tilt = bundle.getDouble(KEY_TILT)
+ )
+
+ fun checkInBundle(bundle: Bundle): Boolean {
+ val chck = bundle.containsKey(KEY_LATITUDE) && bundle.containsKey(KEY_LONGITUDE) && bundle.containsKey(KEY_ZOOM)
+ return chck
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
deleted file mode 100644
--- a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package it.reyboz.bustorino.map
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import it.reyboz.bustorino.backend.Stop
-
-class MapViewModel : ViewModel() {
-
- val currentLat = MutableLiveData(INVALID)
- val currentLong = MutableLiveData(INVALID)
- val currentZoom = MutableLiveData(-10.0)
-
- var stopShowing: Stop? = null
-
- companion object{
- const val INVALID = -1000.0
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
@@ -120,9 +120,6 @@
Log.d(DEBUG_TI, "Switched positions source in ViewModel, now using MQTT: ${!usingMQTT}")
serviceStatus.value = LivePositionsServiceStatus.CONNECTING
}
- fun setGtfsLineToFilterPos(line: String, pattern: MatoPattern?){
- gtfsLineToFilterPos.value = Pair(line, pattern)
- }
var isLastWorkResultGood = workManager
.getWorkInfosForUniqueWorkLiveData(MatoTripsDownloadWorker.TAG_TRIPS).map { it ->
@@ -274,7 +271,7 @@
}
filteredLocationUpdates.addSource(gtfsLineToFilterPos){
- //Log.d(DEBUG_TI, "line to filter change to: ${gtfsLineToFilterPos.value}")
+ Log.d(DEBUG_TI, "line to filter change to: ${gtfsLineToFilterPos.value}")
updatesWithTripAndPatterns.value?.let{
ups-> filteredLocationUpdates.postValue(filterUpdatesForGtfsLine(ups, it))
//Log.d(DEBUG_TI, "Set ${ups.size} updates as new value for filteredLocation")
@@ -282,6 +279,13 @@
}
}
+ private fun clearFilteredPositions(){
+ filteredLocationUpdates.postValue(Pair(HashMap(), ArrayList<String>()))
+ }
+ fun setGtfsLineToFilterPos(line: String, pattern: MatoPattern?){
+ clearFilteredPositions()
+ gtfsLineToFilterPos.value = Pair(line, pattern)
+ }
private fun filterUpdatesForGtfsLine(updates: FullPositionUpdatesMap,
linePatt: Pair<String, MatoPattern?>):
@@ -322,6 +326,7 @@
if (dir == directionId) {
//add the trip
updsForTripId[tripId] = pair
+ Log.d(DEBUG_TI, "Add vehicle ${pair.first.vehicle}, route ${pair.first.routeID}")
} else {
vehicleOnWrongDirection.add(vehicle)
}
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.viewmodels
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import it.reyboz.bustorino.map.MapCameraState
+import org.maplibre.android.camera.CameraPosition
+import org.maplibre.android.geometry.LatLng
+import org.maplibre.android.geometry.LatLngBounds
+import org.maplibre.android.maps.MapLibreMap
+
+class MapStateViewModel : ViewModel() {
+
+ var savedCameraState: MapCameraState? = null
+ private set
+
+ val lastOpenStopID = MutableLiveData<String>()
+
+ fun saveMapState(map: MapLibreMap){
+ val cp = map.cameraPosition
+ val newBbox = map.projection.visibleRegion.latLngBounds
+
+ val cameraState = MapCameraState(
+ latitude = newBbox.center.latitude,
+ longitude = newBbox.center.longitude,
+ zoom = cp.zoom,
+ bearing = cp.bearing,
+ tilt = cp.tilt
+ )
+
+ savedCameraState = cameraState
+ }
+ fun restoreMapState(map: MapLibreMap): Boolean {
+ return restoreMapState(map, this.savedCameraState)
+ }
+
+ companion object{
+ fun restoreMapState(map: MapLibreMap, savedCameraState: MapCameraState?): Boolean {
+ val state = savedCameraState ?: return false
+ map.cameraPosition = CameraPosition.Builder()
+ .target(LatLng(state.latitude, state.longitude))
+ .zoom(state.zoom)
+ .bearing(state.bearing)
+ .tilt(state.tilt)
+ .build()
+ return true
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
@@ -81,11 +81,9 @@
addStopsCallback)
}
}
-
- var savedState: Bundle? = null
var lastUserLocation: Location? = null
companion object{
private const val DEBUG_TAG = "BusTOStopMapViewModel"
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_lines_detail.xml b/app/src/main/res/layout/fragment_lines_detail.xml
--- a/app/src/main/res/layout/fragment_lines_detail.xml
+++ b/app/src/main/res/layout/fragment_lines_detail.xml
@@ -98,27 +98,24 @@
android:src="@drawable/location_circlew_red"
android:layout_marginTop="54dp"
- android:layout_marginEnd="8dp"
+ android:layout_marginEnd="5dp"
android:background="#00ffffff"
android:contentDescription="@string/enable_position"
app:layout_constraintTop_toTopOf="@id/lineMap"
app:layout_constraintEnd_toEndOf="@id/lineMap"
android:cropToPadding="true" />
- <!--
- <ImageButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/icon_follow"
- android:src="@drawable/ic_follow_me"
- android:background="#00ffffff"
- android:contentDescription="@string/bt_follow_me_description"
- android:cropToPadding="true"
- app:layout_constraintEnd_toEndOf="@id/lineMap"
- app:layout_constraintTop_toBottomOf="@id/icon_center_map"
- android:layout_marginTop="10dp"
- android:layout_marginRight="10dp"
- android:layout_marginEnd="10dp"
- />-->
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/busPositionsImageButton"
+ android:src="@drawable/bus_pos_circle_inactive"
+ android:background="#00ffffff"
+ android:contentDescription="@string/bt_follow_me_description"
+ android:layout_marginEnd="5dp"
+
+ app:layout_constraintTop_toBottomOf="@id/locationEnableIcon"
+ app:layout_constraintEnd_toEndOf="@id/lineMap"
+ android:layout_marginTop="7dp" />
<View
@@ -151,5 +148,5 @@
/>
</androidx.constraintlayout.widget.ConstraintLayout>
- <include layout="@layout/map_include_bottom_sheet"/>
+ <include layout="@layout/include_map_bottom_sheet"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map_libre.xml b/app/src/main/res/layout/fragment_map_libre.xml
--- a/app/src/main/res/layout/fragment_map_libre.xml
+++ b/app/src/main/res/layout/fragment_map_libre.xml
@@ -15,7 +15,7 @@
/>
<!-- Bottom Sheet for details -->
- <include layout="@layout/map_include_bottom_sheet"></include>
+ <include layout="@layout/include_map_bottom_sheet"/>
<FrameLayout
android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/map_include_bottom_sheet.xml b/app/src/main/res/layout/include_map_bottom_sheet.xml
rename from app/src/main/res/layout/map_include_bottom_sheet.xml
rename to app/src/main/res/layout/include_map_bottom_sheet.xml
--- a/app/src/main/res/layout/map_include_bottom_sheet.xml
+++ b/app/src/main/res/layout/include_map_bottom_sheet.xml
@@ -59,6 +59,22 @@
android:layout_marginEnd="10dp"
android:fontFamily="@font/lato_regular"
/>
+ <TextView
+ android:id="@+id/extraBottomTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:layout_below="@id/linesPassingTextView"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/arrivalsCardButton"
+ android:layout_marginBottom="5dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="2dp"
+ android:text="Updated at "
+ android:fontFamily="@font/lato_regular"
+ android:visibility="gone"
+ />
<androidx.cardview.widget.CardView
android:id="@+id/arrivalsCardButton"
android:layout_width="50sp"
@@ -117,7 +133,7 @@
android:layout_height="30sp"
app:srcCompat="@drawable/baseline_close_16"
android:id="@+id/btnClose"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="15dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="10dp"
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -279,4 +279,5 @@
<string name="live_positions_dialog_provider_text">Fonte posizioni in tempo reale:</string>
<string name="live_positions_switch_provider">Cambia fonte</string>
<string name="live_positions_preference_switch_clear_title">Rimuovi posizioni sulla mappa quando si cambia fonte delle posizioni in tempo reale</string>
+ <string name="updated_fill">Aggiornato: %1$s</string>
</resources>
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
@@ -373,4 +373,6 @@
<string name="live_positions_switch_provider">Switch source</string>
<string name="live_positions_preference_switch_clear_title">Clear bus positions when switching live positions source</string>
+ <string name="updated_fill">Updated: %1$s</string>
+
</resources>
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@
ext.coroutines_version = "1.10.2"
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.5' // or latest
- classpath 'com.android.tools.build:gradle:8.12.3'
+ classpath 'com.android.tools.build:gradle:8.13.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-2.0.4"
@@ -28,15 +28,15 @@
multidex_version = "2.0.1"
//libraries versions
fragment_version = "1.8.9"
- activity_version = "1.11.0"
+ activity_version = "1.13.0"
appcompat_version = "1.7.1"
preference_version = "1.2.1"
- work_version = "2.11.0"
+ work_version = "2.11.2"
acra_version = "5.13.1"
- lifecycle_version = "2.9.4"
+ lifecycle_version = "2.10.0"
arch_version = "2.1.0"
- room_version = "2.8.3"
+ room_version = "2.8.4"
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Jun 23, 22:07 (21 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1975712
Default Alt Text
D221.1782245225.diff (90 KB)
Attached To
Mode
D221: Add missing features to each map fragment and update libraries
Attached
Detach File
Event Timeline