diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' implementation 'com.android.volley:volley:1.2.0' - implementation 'org.osmdroid:osmdroid-android:6.1.8' + implementation 'org.osmdroid:osmdroid-android:6.1.10' // ACRA implementation "ch.acra:acra-mail:$acra_version" implementation "ch.acra:acra-dialog:$acra_version" diff --git a/src/it/reyboz/bustorino/ActivityMap.java b/src/it/reyboz/bustorino/ActivityMap.java --- a/src/it/reyboz/bustorino/ActivityMap.java +++ b/src/it/reyboz/bustorino/ActivityMap.java @@ -32,6 +32,7 @@ import android.widget.ImageButton; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; @@ -87,6 +88,21 @@ protected ImageButton btCenterMap; protected ImageButton btFollowMe; + private final CustomInfoWindow.TouchResponder touchResponder = new CustomInfoWindow.TouchResponder() { + @Override + public void onActionUp(@NonNull String stopID, @Nullable String stopName) { + Intent intent = new Intent(ctx, ActivityMain.class); + Bundle b = new Bundle(); + b.putString("bus-stop-ID", stopID); + b.putString("bus-stop-display-name", stopName); + intent.putExtras(b); + intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + + // start ActivityMain with the previous intent + ctx.startActivity(intent); + } + }; + //@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) @Override public void onCreate(Bundle savedInstanceState) { @@ -268,7 +284,7 @@ Marker marker = new Marker(map); // set custom info window as info window - CustomInfoWindow popup = new CustomInfoWindow(map, ID, stopName); + CustomInfoWindow popup = new CustomInfoWindow(map, ID, stopName, touchResponder); marker.setInfoWindow(popup); // make the marker clickable @@ -369,6 +385,12 @@ } + @Override + protected void onPostResume() { + super.onPostResume(); + ctx = this; + } + protected boolean detachMapFromPosition(){ if (mLocationOverlay.isFollowLocationEnabled()) { mLocationOverlay.disableFollowLocation(); diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java --- a/src/it/reyboz/bustorino/ActivityPrincipal.java +++ b/src/it/reyboz/bustorino/ActivityPrincipal.java @@ -32,12 +32,14 @@ import java.util.concurrent.TimeUnit; +import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.data.DBUpdateWorker; import it.reyboz.bustorino.data.DatabaseUpdate; import it.reyboz.bustorino.fragments.FavoritesFragment; import it.reyboz.bustorino.fragments.FragmentKind; import it.reyboz.bustorino.fragments.FragmentListenerMain; import it.reyboz.bustorino.fragments.MainScreenFragment; +import it.reyboz.bustorino.fragments.MapFragment; import it.reyboz.bustorino.middleware.GeneralActivity; import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri; @@ -209,6 +211,10 @@ closeDrawerIfOpen(); showMainFragment(); return true; + } else if(menuItem.getItemId() == R.id.nav_map_item){ + closeDrawerIfOpen(); + createAndShowMapFragment(null); + return true; } //selectDrawerItem(menuItem); Log.d(DEBUG_TAG, "pressed item "+menuItem.toString()); @@ -359,6 +365,21 @@ public void showFloatingActionButton(boolean yes) { //TODO } + /* + public void setDrawerSelectedItem(String fragmentTag){ + switch (fragmentTag){ + case MainScreenFragment.FRAGMENT_TAG: + mNavView.setCheckedItem(R.id.nav_arrivals); + break; + case MapFragment.FRAGMENT_TAG: + + break; + + case FavoritesFragment.FRAGMENT_TAG: + mNavView.setCheckedItem(R.id.nav_favorites_item); + break; + } + }*/ @Override public void readyGUIfor(FragmentKind fragmentType) { @@ -366,6 +387,21 @@ if (probableFragment!=null){ probableFragment.readyGUIfor(fragmentType); } + switch (fragmentType){ + case MAP: + mNavView.setCheckedItem(R.id.nav_map_item); + break; + case FAVORITES: + mNavView.setCheckedItem(R.id.nav_favorites_item); + break; + case ARRIVALS: + case NEARBY_STOPS: + case STOPS: + case MAIN_SCREEN_FRAGMENT: + case NEARBY_ARRIVALS: + mNavView.setCheckedItem(R.id.nav_arrivals); + break; + } } @Override @@ -408,6 +444,17 @@ } } + //Map Fragment stuff + void createAndShowMapFragment(@Nullable Stop stop){ + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + MapFragment fragment = stop == null? MapFragment.getInstance(): MapFragment.getInstance(stop); + ft.replace(R.id.mainActContentFrame, fragment, MapFragment.FRAGMENT_TAG); + ft.addToBackStack(null); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.commit(); + } + class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{ @Override diff --git a/src/it/reyboz/bustorino/data/NextGenDB.java b/src/it/reyboz/bustorino/data/NextGenDB.java --- a/src/it/reyboz/bustorino/data/NextGenDB.java +++ b/src/it/reyboz/bustorino/data/NextGenDB.java @@ -157,7 +157,7 @@ Stop[] stops = new Stop[0]; SQLiteDatabase db = this.getReadableDatabase(); - Cursor result; + //Cursor result=null; int count; // coordinates must be strings in the where condition @@ -172,20 +172,19 @@ } try { - result = db.query(StopsTable.TABLE_NAME, QUERY_COLUMN_stops_all, QUERY_WHERE_LAT_AND_LNG_IN_RANGE, + final Cursor result = db.query(StopsTable.TABLE_NAME, QUERY_COLUMN_stops_all, QUERY_WHERE_LAT_AND_LNG_IN_RANGE, new String[] {minLatRaw, maxLatRaw, minLngRaw, maxLngRaw}, null, null, null); stops = getStopsFromCursorAllFields(result); - + result.close(); } catch(SQLiteException e) { Log.e(DEBUG_TAG, "SQLiteException occurred"); e.printStackTrace(); return stops; + }finally { + db.close(); } - result.close(); - db.close(); - return stops; } diff --git a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java --- a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java +++ b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java @@ -51,11 +51,11 @@ } if (firstVisibleItem>=0) { if (lastvisibleitem < firstVisibleItem) { - Log.i("Busto", "Scrolling DOWN"); + //Log.i("Busto", "Scrolling DOWN"); listener.showFloatingActionButton(false); //lastScrollUp = true; } else if (lastvisibleitem > firstVisibleItem) { - Log.i("Busto", "Scrolling UP"); + //Log.i("Busto", "Scrolling UP"); listener.showFloatingActionButton(true); //lastScrollUp = false; } diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java --- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java +++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java @@ -45,6 +45,8 @@ @Nullable private CommonFragmentListener mListener; + public static final String FRAGMENT_TAG = "BusTOFavFragment"; + @@ -56,7 +58,7 @@ fragment.setArguments(args); return fragment; } - private FavoritesFragment(){ + public FavoritesFragment(){ } @@ -123,6 +125,12 @@ } } + @Override + public void onResume() { + super.onResume(); + if (mListener!=null) mListener.readyGUIfor(FragmentKind.FAVORITES); + } + @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item @@ -177,6 +185,7 @@ } } + void showStops(List busStops){ // If no data is found show a friendly message diff --git a/src/it/reyboz/bustorino/fragments/FragmentKind.java b/src/it/reyboz/bustorino/fragments/FragmentKind.java --- a/src/it/reyboz/bustorino/fragments/FragmentKind.java +++ b/src/it/reyboz/bustorino/fragments/FragmentKind.java @@ -18,5 +18,5 @@ package it.reyboz.bustorino.fragments; public enum FragmentKind { - STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS + STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS, MAP, MAIN_SCREEN_FRAGMENT } diff --git a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java --- a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java +++ b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java @@ -23,14 +23,7 @@ void toggleSpinner(boolean state); - - /* - Unused method - * Add the last successfully searched stop to the favorites - */ - - //void toggleLastStopToFavorites(); - + //TODO: implement void showStopOnMap() /** * Tell activity that we need to enable/disable the refreshLayout diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -318,6 +318,7 @@ requestArrivalsForStopID(pendingStopID); pendingStopID = null; } + mListener.readyGUIfor(FragmentKind.MAIN_SCREEN_FRAGMENT); } @Override @@ -326,6 +327,7 @@ locmgr = null; super.onPause(); } + /* GUI METHODS */ @@ -490,7 +492,7 @@ prepareGUIForBusStops(); break; default: - Log.e("BusTO Activity", "Called readyGUI with unsupported type of Fragment"); + Log.d(DEBUG_TAG, "Fragment type is unknown"); return; } // Shows hints diff --git a/src/it/reyboz/bustorino/fragments/MapFragment.java b/src/it/reyboz/bustorino/fragments/MapFragment.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/fragments/MapFragment.java @@ -0,0 +1,532 @@ +package it.reyboz.bustorino.fragments; + +import android.Manifest; +import android.content.Context; + +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.res.ResourcesCompat; +import androidx.preference.PreferenceManager; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.api.IMapController; +import org.osmdroid.config.Configuration; +import org.osmdroid.events.DelayedMapListener; +import org.osmdroid.events.MapListener; +import org.osmdroid.events.ScrollEvent; +import org.osmdroid.events.ZoomEvent; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.util.BoundingBox; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.FolderOverlay; +import org.osmdroid.views.overlay.Marker; +import org.osmdroid.views.overlay.infowindow.InfoWindow; +import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.data.NextGenDB; +import it.reyboz.bustorino.map.CustomInfoWindow; +import it.reyboz.bustorino.map.LocationOverlay; +import it.reyboz.bustorino.middleware.GeneralActivity; + +import static it.reyboz.bustorino.util.Permissions.PERMISSION_REQUEST_POSITION; + +public class MapFragment extends BaseFragment { + + private static final String TAG = "Busto-MapActivity"; + private static final String MAP_CURRENT_ZOOM_KEY = "map-current-zoom"; + private static final String MAP_CENTER_LAT_KEY = "map-center-lat"; + private static final String MAP_CENTER_LON_KEY = "map-center-lon"; + private static final String FOLLOWING_LOCAT_KEY ="following"; + + public static final String BUNDLE_LATIT = "lat"; + public static final String BUNDLE_LONGIT = "lon"; + public static final String BUNDLE_NAME = "name"; + public static final String BUNDLE_ID = "ID"; + + public static final String FRAGMENT_TAG="BusTOMapFragment"; + + + private static final double DEFAULT_CENTER_LAT = 45.0708; + private static final double DEFAULT_CENTER_LON = 7.6858; + private static final double POSITION_FOUND_ZOOM = 18.3; + + private static final String DEBUG_TAG=FRAGMENT_TAG; + + protected FragmentListenerMain listenerMain; + + private HashSet shownStops = null; + + + private MapView map = null; + public Context ctx; + private LocationOverlay mLocationOverlay = null; + private FolderOverlay stopsFolderOverlay = null; + private Bundle savedMapState = null; + protected ImageButton btCenterMap; + protected ImageButton btFollowMe; + private boolean followingLocation = false; + + protected final CustomInfoWindow.TouchResponder responder = new CustomInfoWindow.TouchResponder() { + @Override + public void onActionUp(@NonNull String stopID, @Nullable String stopName) { + if (listenerMain!= null){ + listenerMain.requestArrivalsForStopID(stopID); + } + } + }; + protected final LocationOverlay.OverlayCallbacks locationCallbacks = new LocationOverlay.OverlayCallbacks() { + @Override + public void onDisableFollowMyLocation() { + updateGUIForLocationFollowing(false); + followingLocation=false; + } + + @Override + public void onEnableFollowMyLocation() { + updateGUIForLocationFollowing(true); + followingLocation=true; + } + }; + + public MapFragment() { + } + public static MapFragment getInstance(){ + return new MapFragment(); + } + public static MapFragment getInstance(double stopLatit, double stopLong, String stopName, String stopID){ + MapFragment fragment= new MapFragment(); + Bundle args = new Bundle(); + args.putDouble(BUNDLE_LATIT, stopLatit); + args.putDouble(BUNDLE_LONGIT, stopLong); + args.putString(BUNDLE_NAME, stopName); + args.putString(BUNDLE_ID, stopID); + fragment.setArguments(args); + + return fragment; + } + public static MapFragment getInstance(Stop stop){ + return getInstance(stop.getLatitude(), stop.getLongitude(), stop.getStopDisplayName(), stop.ID); + } + + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + //use the same layout as the activity + View root = inflater.inflate(R.layout.activity_map, container, false); + if (getContext() == null){ + throw new IllegalStateException(); + } + ctx = getContext().getApplicationContext(); + Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx)); + map = root.findViewById(R.id.map); + map.setTileSource(TileSourceFactory.MAPNIK); + //map.setTilesScaledToDpi(true); + map.setFlingEnabled(true); + + // add ability to zoom with 2 fingers + map.setMultiTouchControls(true); + + btCenterMap = root.findViewById(R.id.ic_center_map); + btFollowMe = root.findViewById(R.id.ic_follow_me); + + //setup FolderOverlay + stopsFolderOverlay = new FolderOverlay(); + + + //Start map from bundle + if (savedInstanceState !=null) + startMap(getArguments(), savedInstanceState); + else startMap(getArguments(), savedMapState); + //set listeners + map.addMapListener(new DelayedMapListener(new MapListener() { + + @Override + public boolean onScroll(ScrollEvent paramScrollEvent) { + requestStopsToShow(); + //Log.d(DEBUG_TAG, "Scrolling"); + //if (moveTriggeredByCode) moveTriggeredByCode =false; + //else setLocationFollowing(false); + return true; + } + + @Override + public boolean onZoom(ZoomEvent event) { + requestStopsToShow(); + return true; + } + + })); + + + btCenterMap.setOnClickListener(v -> { + //Log.i(TAG, "centerMap clicked "); + final GeoPoint myPosition = mLocationOverlay.getMyLocation(); + map.getController().animateTo(myPosition); + }); + + btFollowMe.setOnClickListener(v -> { + //Log.i(TAG, "btFollowMe clicked "); + switchLocationFollowing(!followingLocation); + }); + + return root; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + + if (context instanceof FragmentListenerMain) { + listenerMain = (FragmentListenerMain) context; + } else { + throw new RuntimeException(context.toString() + + " must implement FragmentListenerMain"); + } + } + @Override + public void onDetach() { + super.onDetach(); + listenerMain = null; + // setupOnAttached = true; + Log.w(DEBUG_TAG, "Fragment detached"); + } + + @Override + public void onPause() { + super.onPause(); + saveMapState(); + } + + /** + * Save the map state inside the fragment + * (calls saveMapState(bundle)) + */ + private void saveMapState(){ + savedMapState = new Bundle(); + saveMapState(savedMapState); + + } + + /** + * Save the state of the map to restore it to a later time + * @param bundle the bundle in which to save the data + */ + private void saveMapState(Bundle bundle){ + final IGeoPoint loc = map.getMapCenter(); + bundle.putDouble(MAP_CENTER_LAT_KEY, loc.getLatitude()); + bundle.putDouble(MAP_CENTER_LON_KEY, loc.getLongitude()); + bundle.putDouble(MAP_CURRENT_ZOOM_KEY, map.getZoomLevelDouble()); + Log.d(DEBUG_TAG, "Saving state, location following: "+followingLocation); + bundle.putBoolean(FOLLOWING_LOCAT_KEY, followingLocation); + + } + + @Override + public void onResume() { + super.onResume(); + if(listenerMain!=null) listenerMain.readyGUIfor(FragmentKind.MAP); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + saveMapState(outState); + + super.onSaveInstanceState(outState); + } + + //own methods + + /** + * Switch following the location on and off + * @param value true if we want to follow location + */ + public void switchLocationFollowing(Boolean value){ + followingLocation = value; + if (value){ + mLocationOverlay.enableFollowLocation(); + } else { + mLocationOverlay.disableFollowLocation(); + } + } + + /** + * Do all the stuff you need to do on the gui, when parameter is changed to value + * @param following value + */ + protected void updateGUIForLocationFollowing(boolean following){ + if (following) + btFollowMe.setImageResource(R.drawable.ic_follow_me_on); + else + btFollowMe.setImageResource(R.drawable.ic_follow_me); + + } + + public void startMap(Bundle incoming, Bundle savedInstanceState) { + //Check that we're attached + GeneralActivity activity = getActivity() instanceof GeneralActivity ? (GeneralActivity) getActivity() : null; + if(getContext()==null|| activity==null){ + //we are not attached + Log.e(DEBUG_TAG, "Calling startMap when not attached"); + return; + }else{ + Log.d(DEBUG_TAG, "Starting map from scratch"); + } + + + //parse incoming bundle + GeoPoint marker = null; + String name = null; + String ID = null; + if (incoming != null) { + double lat = incoming.getDouble(BUNDLE_LATIT); + double lon = incoming.getDouble(BUNDLE_LONGIT); + marker = new GeoPoint(lat, lon); + name = incoming.getString(BUNDLE_NAME); + ID = incoming.getString(BUNDLE_ID); + } + + shownStops = new HashSet<>(); + // move the map on the marker position or on a default view point: Turin, Piazza Castello + // and set the start zoom + IMapController mapController = map.getController(); + GeoPoint startPoint = null; + + boolean havePositionPermission = true; + + if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + activity.askForPermissionIfNeeded(Manifest.permission.ACCESS_FINE_LOCATION, PERMISSION_REQUEST_POSITION); + havePositionPermission = false; + } + // Location Overlay + // from OpenBikeSharing (THANK GOD) + GpsMyLocationProvider imlp = new GpsMyLocationProvider(activity.getBaseContext()); + imlp.setLocationUpdateMinDistance(5); + imlp.setLocationUpdateMinTime(2000); + this.mLocationOverlay = new LocationOverlay(imlp,map, locationCallbacks); + mLocationOverlay.enableMyLocation(); + mLocationOverlay.setOptionsMenuEnabled(true); + + if (marker != null) { + startPoint = marker; + mapController.setZoom(POSITION_FOUND_ZOOM); + switchLocationFollowing(false); + } else if (savedInstanceState != null) { + mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY)); + mapController.setCenter(new GeoPoint(savedInstanceState.getDouble(MAP_CENTER_LAT_KEY), + savedInstanceState.getDouble(MAP_CENTER_LON_KEY))); + Log.d(DEBUG_TAG, "Location following from savedInstanceState: "+savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); + switchLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); + } else { + Log.d(DEBUG_TAG, "No position found from intent or saved state"); + boolean found = false; + LocationManager locationManager = + (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); + if (locationManager != null) { + + Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (userLocation != null) { + mapController.setZoom(POSITION_FOUND_ZOOM); + startPoint = new GeoPoint(userLocation); + found = true; + switchLocationFollowing(true); + } + } + if(!found){ + startPoint = new GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON); + mapController.setZoom(16.0); + switchLocationFollowing(false); + } + } + + // set the minimum zoom level + map.setMinZoomLevel(15.0); + //add contingency check (shouldn't happen..., but) + if (startPoint != null) { + mapController.setCenter(startPoint); + } + + + + map.getOverlays().add(this.mLocationOverlay); + + //add stops overlay + map.getOverlays().add(this.stopsFolderOverlay); + + Log.d(DEBUG_TAG, "Requesting stops load"); + // This is not necessary, by setting the center we already move + // the map and we trigger a stop request + //requestStopsToShow(); + if (marker != null) { + // make a marker with the info window open for the searched marker + makeMarker(startPoint, name , ID, true); + } + + } + + /** + * Start a request to load the stops that are in the current view + * from the database + */ + private void requestStopsToShow(){ + // get the top, bottom, left and right screen's coordinate + BoundingBox bb = map.getBoundingBox(); + double latFrom = bb.getLatSouth(); + double latTo = bb.getLatNorth(); + double lngFrom = bb.getLonWest(); + double lngTo = bb.getLonEast(); + + new AsyncStopFetcher(this).execute( + new AsyncStopFetcher.BoundingBoxLimit(lngFrom,lngTo,latFrom, latTo)); + } + + /** + * Add stops as Markers on the map + * @param stops the list of stops that must be included + */ + protected void showStopsMarkers(List stops){ + + for (Stop stop : stops) { + if (shownStops.contains(stop.ID)){ + continue; + } + if(stop.getLongitude()==null || stop.getLatitude()==null) + continue; + + shownStops.add(stop.ID); + GeoPoint marker = new GeoPoint(stop.getLatitude(), stop.getLongitude()); + Marker stopMarker = makeMarker(marker, stop.getStopDefaultName(), stop.ID, false); + stopsFolderOverlay.add(stopMarker); + if (!map.getOverlays().contains(stopsFolderOverlay)) { + Log.w(DEBUG_TAG, "Map doesn't have folder overlay"); + } + } + //Log.d(DEBUG_TAG,"We have " +stopsFolderOverlay.getItems().size()+" stops in the folderOverlay"); + //force redraw of markers + map.invalidate(); + } + + public Marker makeMarker(GeoPoint geoPoint, String stopName, String ID, boolean isStartMarker) { + + // add a marker + Marker marker = new Marker(map); + + // set custom info window as info window + CustomInfoWindow popup = new CustomInfoWindow(map, ID, stopName, responder); + marker.setInfoWindow(popup); + + // make the marker clickable + marker.setOnMarkerClickListener((thisMarker, mapView) -> { + if (thisMarker.isInfoWindowOpen()) { + // on second click + //TODO: show the arrivals for the stop + Log.w(DEBUG_TAG, "Pressed on the click marker"); + } else { + // on first click + + // hide all opened info window + InfoWindow.closeAllInfoWindowsOn(map); + // show this particular info window + thisMarker.showInfoWindow(); + // move the map to its position + map.getController().animateTo(thisMarker.getPosition()); + } + + return true; + }); + + // set its position + marker.setPosition(geoPoint); + marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM); + // add to it an icon + //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker)); + + marker.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.bus_marker, ctx.getTheme())); + // add to it a title + marker.setTitle(stopName); + // set the description as the ID + marker.setSnippet(ID); + + // show popup info window of the searched marker + if (isStartMarker) { + marker.showInfoWindow(); + } + + return marker; + } + + /** + * Simple asyncTask class to load the stops in the background + * Holds a weak reference to the fragment to do callbacks + */ + static class AsyncStopFetcher extends AsyncTask>{ + + final WeakReference fragmentWeakReference; + + public AsyncStopFetcher(MapFragment fragment) { + this.fragmentWeakReference = new WeakReference<>(fragment); + } + + @Override + protected List doInBackground(BoundingBoxLimit... limits) { + if(fragmentWeakReference.get()==null || fragmentWeakReference.get().getContext() == null){ + Log.w(DEBUG_TAG, "AsyncLoad fragmentWeakreference null"); + + return null; + + } + final BoundingBoxLimit limit = limits[0]; + //Log.d(DEBUG_TAG, "Async Stop Fetcher started working"); + + NextGenDB dbHelper = new NextGenDB(fragmentWeakReference.get().getContext()); + Stop[] stops = dbHelper.queryAllInsideMapView(limit.latitFrom, limit.latitTo, + limit.longFrom, limit.latitTo); + dbHelper.close(); + return Arrays.asList(stops); + } + + @Override + protected void onPostExecute(List stops) { + super.onPostExecute(stops); + //Log.d(DEBUG_TAG, "Async Stop Fetcher has finished working"); + if(fragmentWeakReference.get()==null) { + Log.w(DEBUG_TAG, "AsyncLoad fragmentWeakreference null"); + return; + } + Log.d(DEBUG_TAG, "AsyncLoad number of stops: "+stops.size()); + fragmentWeakReference.get().showStopsMarkers(stops); + } + + private static class BoundingBoxLimit{ + final double longFrom, longTo, latitFrom, latitTo; + + public BoundingBoxLimit(double longFrom, double longTo, double latitFrom, double latitTo) { + this.longFrom = longFrom; + this.longTo = longTo; + this.latitFrom = latitFrom; + this.latitTo = latitTo; + } + } + + } +} diff --git a/src/it/reyboz/bustorino/map/CustomInfoWindow.java b/src/it/reyboz/bustorino/map/CustomInfoWindow.java --- a/src/it/reyboz/bustorino/map/CustomInfoWindow.java +++ b/src/it/reyboz/bustorino/map/CustomInfoWindow.java @@ -1,27 +1,28 @@ package it.reyboz.bustorino.map; import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; +import android.os.Build; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; -import org.osmdroid.api.IMapView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.osmdroid.views.MapView; import org.osmdroid.views.overlay.infowindow.BasicInfoWindow; -import it.reyboz.bustorino.ActivityMain; import it.reyboz.bustorino.R; public class CustomInfoWindow extends BasicInfoWindow { + //TODO: Make the action on the Click customizable + private final TouchResponder touchResponder; + private final String stopID, name; @Override public void onOpen(Object item) { super.onOpen(item); - TextView descr_textView = (TextView) mView.findViewById(R.id.bubble_description); CharSequence text = descr_textView.getText(); if (text==null || !text.toString().isEmpty()){ @@ -29,32 +30,35 @@ } else descr_textView.setVisibility(View.GONE); - mView.setElevation(3.2f); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mView.setElevation(3.2f); + } } @SuppressLint("ClickableViewAccessibility") - public CustomInfoWindow(MapView mapView, String ID, String stopName) { + public CustomInfoWindow(MapView mapView, String stopID, String name, TouchResponder responder) { // get the personalized layout super(R.layout.map_popup, mapView); + touchResponder =responder; + this.stopID = stopID; + this.name = name; // make clickable mView.setOnTouchListener((View v, MotionEvent e) -> { if (e.getAction() == MotionEvent.ACTION_UP) { // on click - - // create an intent with these extras - Intent intent = new Intent(mapView.getContext(), ActivityMain.class); - Bundle b = new Bundle(); - b.putString("bus-stop-ID", ID); - b.putString("bus-stop-display-name", stopName); - intent.putExtras(b); - intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - - // start ActivityMain with the previous intent - mapView.getContext().startActivity(intent); + touchResponder.onActionUp(stopID, name); } return true; }); } + public interface TouchResponder{ + /** + * React to a click on the stop View + * @param stopID the stop id + * @param stopName the stop name + */ + void onActionUp(@NonNull String stopID, @Nullable String stopName); + } } diff --git a/src/it/reyboz/bustorino/map/LocationOverlay.java b/src/it/reyboz/bustorino/map/LocationOverlay.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/map/LocationOverlay.java @@ -0,0 +1,62 @@ +/* + BusTO (middleware) + Copyright (C) 2021 Fabio Mazza + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino.map; + + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.mylocation.IMyLocationProvider; +import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; + +public class LocationOverlay extends MyLocationNewOverlay { + + final OverlayCallbacks callbacks; + + public LocationOverlay(MapView mapView, OverlayCallbacks callbacks) { + super(mapView); + this.callbacks = callbacks; + } + + public LocationOverlay(IMyLocationProvider myLocationProvider, MapView mapView, OverlayCallbacks callbacks) { + super(myLocationProvider, mapView); + this.callbacks = callbacks; + } + + @Override + public void enableFollowLocation() { + super.enableFollowLocation(); + callbacks.onEnableFollowMyLocation(); + } + + @Override + public void disableFollowLocation() { + super.disableFollowLocation(); + callbacks.onDisableFollowMyLocation(); + } + + public interface OverlayCallbacks{ + /** + * Called right after disableFollowMyLocation + */ + void onDisableFollowMyLocation(); + + /** + * Called right after enableFollowMyLocation + */ + void onEnableFollowMyLocation(); + } +} diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java @@ -45,6 +45,7 @@ public class AsyncDataDownload extends AsyncTask{ private static final String TAG = "BusTO-DataDownload"; + private static final String DEBUG_TAG = TAG; private boolean failedAll = false; private final AtomicReference res; @@ -212,9 +213,24 @@ break; case STOPS: //this should never be a problem - List stopList = (List) o; + if(!(o instanceof List)){ + throw new IllegalStateException(); + } + List list = (List) o; + if (list.size() ==0) return; + Object firstItem = list.get(0); + if(!(firstItem instanceof Stop)) return; + ArrayList stops = new ArrayList<>(); + for(Object x: list){ + if(x instanceof Stop) stops.add((Stop) x); + } + if(list.size() != stops.size()){ + Log.w(DEBUG_TAG, "Wrong stop list size:\n incoming: "+ + list.size()+" out: "+stops.size()); + } + //List stopList = (List) list; if(query!=null && !isCancelled()) { - fh.createStopListFragment(stopList,query, replaceFragment); + fh.createStopListFragment(stops,query, replaceFragment); } else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT"); break; case DBUPDATE: @@ -239,20 +255,21 @@ ARRIVALS,STOPS,DBUPDATE } - public class BranchInserter implements Runnable{ + public static class BranchInserter implements Runnable{ private final List routesToInsert; private final Context context; - private final NextGenDB nextGenDB; + //private final NextGenDB nextGenDB; public BranchInserter(List routesToInsert,@NonNull Context con) { this.routesToInsert = routesToInsert; - this.context = con; - nextGenDB = new NextGenDB(context); + this.context = con.getApplicationContext(); + //nextGenDB = new NextGenDB(context); } @Override public void run() { + final NextGenDB nextGenDB = new NextGenDB(context); ContentValues[] values = new ContentValues[routesToInsert.size()]; ArrayList connectionsVals = new ArrayList<>(routesToInsert.size()*4); long starttime,endtime; @@ -324,6 +341,7 @@ int rows = nextGenDB.insertBatchContent(valArr,ConnectionsTable.TABLE_NAME); endtime = System.currentTimeMillis(); Log.d("DataDownload","Inserted connections found, took "+(endtime-starttime)+" ms, inserted "+rows+" rows"); + nextGenDB.close(); } } }