Page Menu
Home
GitPull.it
Search
Configure Global Search
Log In
Files
F13558696
D239.1783795476.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Authored By
Unknown
Size
122 KB
Referenced Files
None
Subscribers
None
D239.1783795476.diff
View Options
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
@@ -53,7 +53,6 @@
import it.reyboz.bustorino.data.DBUpdateCheckWorker;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.PreferencesHolder;
-import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.GeneralActivity;
import it.reyboz.bustorino.viewmodels.ServiceAlertsViewModel;
@@ -74,10 +73,22 @@
private boolean onCreateComplete = false;
private ServiceAlertsViewModel serviceAlertsViewModel;
- private final OnBackPressedCallback callback = new OnBackPressedCallback(false) {
+
+ private long lastClosingAttempt = -1L;
+ private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
- activityCustomBackPressed();
+ boolean isResolved = activityCustomBackPressed();
+ Log.d(DEBUG_TAG, "backpress resolved: " + isResolved);
+ if(!isResolved){
+ long currentTime = System.currentTimeMillis();
+ if(currentTime - lastClosingAttempt < 2000){
+ finish();
+ } else{
+ lastClosingAttempt = currentTime;
+ Toast.makeText(getApplicationContext(),R.string.back_again_to_close,Toast.LENGTH_SHORT).show();
+ }
+ }
}
};
@@ -95,8 +106,8 @@
*/
//onBackPressed solution required from Android 16
- callback.setEnabled(true);
- this.getOnBackPressedDispatcher().addCallback( callback);
+ backPressedCallback.setEnabled(true);
+ this.getOnBackPressedDispatcher().addCallback(backPressedCallback);
boolean showingArrivalsFromIntent = false;
final Toolbar mToolbar = findViewById(R.id.default_toolbar);
@@ -218,7 +229,7 @@
}
}
if (showProgress) {
- createDefaultSnackbar();
+ createDatabaseUpdateSnackbar();
} else {
if(snackbar!=null) {
snackbar.dismiss();
@@ -493,8 +504,7 @@
* Create and show the SnackBar with the message
* The fragment shown points to which view to attach the snackbar
*/
- private void createDefaultSnackbar() {
-
+ private void createDatabaseUpdateSnackbar() {
View baseView = null;
boolean showSnackbar = true;
final Fragment frag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
@@ -505,11 +515,13 @@
if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
//if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
if (baseView !=null && showSnackbar) {
- this.snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
+ snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
+ snackbar.setTextColor(getColor(android.R.color.white));
+ snackbar.setBackgroundTint(getColor(R.color.grey_800));
if (frag instanceof ScreenBaseFragment){
- ((ScreenBaseFragment) frag).setSnackbarPropertiesBeforeShowing(this.snackbar);
+ ((ScreenBaseFragment) frag).setSnackbarPropertiesBeforeShowing(snackbar);
}
- this.snackbar.show();
+ snackbar.show();
} else{
Log.e(DEBUG_TAG, "Asked to show the snackbar but the baseView is null");
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Result.java b/app/src/main/java/it/reyboz/bustorino/backend/Result.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Result.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Result.java
@@ -1,5 +1,6 @@
package it.reyboz.bustorino.backend;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -14,7 +15,7 @@
}
- public static <T> Result<T> success(@Nullable T result) {
+ public static <T> Result<T> success(@NonNull T result) {
return new Result<>(result);
}
@@ -25,7 +26,7 @@
return new Result<>(error);
}
- private Result(@Nullable T result) {
+ private Result(@NonNull T result) {
this.result = result;
this.exception = null;
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/StopFavoritesData.kt b/app/src/main/java/it/reyboz/bustorino/backend/StopFavoritesData.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/backend/StopFavoritesData.kt
@@ -0,0 +1,18 @@
+package it.reyboz.bustorino.backend
+
+import android.util.Log
+
+data class StopFavoritesData(
+ val stopID: String,
+ val stopUserName: String? = null
+) {
+ constructor(s: Stop) : this(s.ID,s.stopUserName)
+
+ fun addToStop(s: Stop) {
+ if(s.ID!=stopID){
+ Log.e("BusTO-FavoritesData", "Trying to add info to stop with different ID")
+ } else{
+ s.stopUserName =stopUserName
+ }
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java b/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java
--- a/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/AppDataProvider.java
@@ -28,13 +28,12 @@
import androidx.annotation.Nullable;
import it.reyboz.bustorino.BuildConfig;
import it.reyboz.bustorino.backend.DBStatusManager;
-import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import java.util.List;
-import static it.reyboz.bustorino.data.UserDB.getFavoritesColumnNamesAsArray;
+import static it.reyboz.bustorino.data.UserDB.FAVORITES_COLUMNS_ARRAY;
public class AppDataProvider extends ContentProvider {
@@ -249,7 +248,7 @@
}
case FAVORITES_OP:
- final String stopFavSelection = getFavoritesColumnNamesAsArray[0]+" = ?";
+ final String stopFavSelection = FAVORITES_COLUMNS_ARRAY[0]+" = ?";
db = userDBHelper.getReadableDatabase();
Log.d(DEBUG_TAG,"Asked information on Favorites about stop with id "+uri.getLastPathSegment());
return db.query(UserDB.TABLE_NAME,projection,stopFavSelection,new String[]{uri.getLastPathSegment()},null,null,sortOrder);
diff --git a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.kt
@@ -19,8 +19,6 @@
import android.annotation.SuppressLint
import android.content.Context
-import android.content.pm.ServiceInfo
-import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@@ -83,7 +81,7 @@
when (resultUpdate) {
DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD -> dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_STOPS)
DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD -> dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES)
- DatabaseUpdate.Result.DB_CLOSED -> dataBuilder.put(ERROR_REASON_KEY, ERROR_CODE_DB_CLOSED)
+ DatabaseUpdate.Result.DATABASE_ERROR -> dataBuilder.put(ERROR_REASON_KEY, ERROR_CODE_DATABASE)
DatabaseUpdate.Result.DONE -> {}
}
//cancelNotification(NOTIFICATION_ID)
@@ -151,7 +149,7 @@
const val ERROR_FETCHING_VERSION: Int = 4
const val ERROR_DOWNLOADING_STOPS: Int = 5
const val ERROR_DOWNLOADING_LINES: Int = 6
- val ERROR_CODE_DB_CLOSED: Int = -2
+ val ERROR_CODE_DATABASE: Int = -2
const val SUCCESS_REASON_KEY: String = "SUCCESS_REASON"
const val SUCCESS_NO_ACTION_NEEDED: Int = 9
diff --git a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java
--- a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java
@@ -17,10 +17,8 @@
*/
package it.reyboz.bustorino.data;
-import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
-import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -58,9 +56,9 @@
-
+ //todo: do something with this enum to show the user
public enum Result {
- DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD, DB_CLOSED
+ DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD, DATABASE_ERROR
}
/**
@@ -219,62 +217,12 @@
}
final NextGenDB dbHelp = NextGenDB.getInstance(con.getApplicationContext());
- final SQLiteDatabase db = dbHelp.getWritableDatabase();
-
- if(!db.isOpen()){
- //catch errors like: java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
- //we have to abort the work and restart it
- return Result.DB_CLOSED;
- }
- //TODO: Get the type of stop from the lines
- //Empty the needed tables
-
- db.beginTransaction();
- //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
- //db.delete(LinesTable.TABLE_NAME,null,null);
-
- //put new data
- long startTime = System.currentTimeMillis();
-
- Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
- String routesStoppingString="";
- int patternsStopsHits = 0;
- for (final Palina p : palinasMatoAPI) {
- final ContentValues cv = new ContentValues();
-
- cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
- cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
- if (p.location != null)
- cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
- cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
- cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
- if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
- if(p.gtfsID!= null && routesStoppingByStop.containsKey(p.gtfsID)){
- final ArrayList<String> routesSs= new ArrayList<>(routesStoppingByStop.get(p.gtfsID));
- routesStoppingString = Palina.buildRoutesStringFromNames(routesSs);
- patternsStopsHits++;
- } else{
- routesStoppingString = p.routesThatStopHereToString();
- }
- cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, routesStoppingString);
- if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
- if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
- //Log.d(DEBUG_TAG,cv.toString());
- //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
- //valuesArr[i] = cv;
- db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
-
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- long endTime = System.currentTimeMillis();
- Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
- Log.d(DEBUG_TAG, "\t"+patternsStopsHits+" routes string were built from the patterns");
- // These should NOT be closed: the database is a singleton, the connections are recycled.
- //db.close();
- //dbHelp.close();
-
- return DatabaseUpdate.Result.DONE;
+ //final SQLiteDatabase db = dbHelp.getWritableDatabase();
+ boolean done= dbHelp.updateDataStops(palinasMatoAPI, routesStoppingByStop);
+ if(done)
+ return DatabaseUpdate.Result.DONE;
+ else
+ return Result.DATABASE_ERROR;
}
public static boolean setDBUpdatingFlag(Context con, boolean value){
diff --git a/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java b/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
--- a/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.database.DatabaseErrorHandler;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -33,7 +34,6 @@
import androidx.lifecycle.LiveData;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -75,7 +75,7 @@
}
- private void loadData() {
+ public void loadData() {
loadData(false);
}
private static Uri.Builder getStopsBuilder(){
@@ -100,9 +100,19 @@
}
isQueryRunning = true;
- queryHandler.startQuery(FAV_TOKEN,null, FAVORITES_URI, UserDB.getFavoritesColumnNamesAsArray, null, null, null);
+ startQuery();
+ }
+
+ private void startQuery(){
+ Log.d(TAG, "startQuery for token "+FAV_TOKEN);
+ queryHandler.startQuery(FAV_TOKEN,null, FAVORITES_URI, UserDB.FAVORITES_COLUMNS_ARRAY, null, null, null);
+
+ }
+ public void stopQuery(){
+ queryHandler.cancelOperation(FAV_TOKEN);
+ isQueryRunning = false;
}
public void forceReload(){
@@ -140,17 +150,24 @@
@Override
public void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (cursor == null){
- //Nothing to do
Log.e(TAG, "Null cursor for token "+token);
+ if(token == FAV_TOKEN){
+ //restart query
+ Log.d(TAG, "Restarting query");
+ queryHandler.cancelOperation(FAV_TOKEN);
+
+ isQueryRunning = false;
+ loadData(true);
+ }
return;
}
if (token == FAV_TOKEN) {
- stopsFromFavorites = UserDB.getFavoritesFromCursor(cursor, UserDB.getFavoritesColumnNamesAsArray);
+ stopsFromFavorites = UserDB.getFavoritesFromCursor(cursor, UserDB.FAVORITES_COLUMNS_ARRAY);
cursor.close();
//reset counters
stopNeededCount = stopsFromFavorites.size();
stopsDone = new ArrayList<>();
- if(stopsFromFavorites.size() == 0){
+ if(stopsFromFavorites.isEmpty()){
//we don't need to call the other query
setValue(stopsDone);
isQueryRunning = false;
diff --git a/app/src/main/java/it/reyboz/bustorino/data/FavoritesViewModel.java b/app/src/main/java/it/reyboz/bustorino/data/FavoritesViewModel.java
deleted file mode 100644
--- a/app/src/main/java/it/reyboz/bustorino/data/FavoritesViewModel.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package it.reyboz.bustorino.data;
-
-import android.app.Application;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-
-import java.util.List;
-
-import it.reyboz.bustorino.backend.Stop;
-
-public class FavoritesViewModel extends AndroidViewModel {
-
- FavoritesLiveData favoritesLiveData;
-
- public FavoritesViewModel(@NonNull Application application) {
- super(application);
- //appContext = application.getApplicationContext();
- }
-
- @Override
- protected void onCleared() {
- favoritesLiveData.onClear();
- super.onCleared();
- }
-
- public FavoritesLiveData getFavorites(){
- if (favoritesLiveData==null){
- favoritesLiveData= new FavoritesLiveData(getApplication(), true);
- }
- return favoritesLiveData;
- }
-}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/InvalidationTracker.kt b/app/src/main/java/it/reyboz/bustorino/data/InvalidationTracker.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/data/InvalidationTracker.kt
@@ -0,0 +1,34 @@
+package it.reyboz.bustorino.data
+
+import android.util.Log
+import it.reyboz.bustorino.BuildConfig
+
+/**
+ * Invalidation tracker to use to make auto-updating LiveData from SQLite database
+ */
+class InvalidationTracker {
+ private val tableObservers = mutableMapOf<String, MutableSet<Observer>>()
+
+ fun addObserver(table: String, onInvalidate: Observer) {
+ tableObservers.getOrPut(table) { mutableSetOf() }.add(onInvalidate)
+ }
+
+ fun removeObserver(table: String, onInvalidate: Observer) {
+ tableObservers[table]?.remove(onInvalidate)
+ }
+
+ fun notifyInvalidation(vararg tables: String) {
+ if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "gitpull")
+ Log.d(DEBUG_TAG, "invalidating tables: ${tables.contentToString()}")
+ tables.forEach { table ->
+ tableObservers[table]?.forEach { it.onInvalidate() }
+ }
+ }
+
+ fun interface Observer {
+ fun onInvalidate()
+ }
+ companion object {
+ const val DEBUG_TAG = "BusTO-InvalidTracker"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
--- a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
@@ -28,7 +28,9 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import it.reyboz.bustorino.backend.Palina;
+import it.reyboz.bustorino.backend.Result;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
@@ -91,9 +93,10 @@
public static final String QUERY_FROM_GTFS_ID_IN_TO_COMPLETE= StopsTable.COL_GTFS_ID +" IN ";
public static String QUERY_WHERE_ID = StopsTable.COL_ID+" = ?";
-
+ public static final int RESULT_SQLITE_ERROR = -4;
private final Context appContext;
+ private final InvalidationTracker invalidationTracker = new InvalidationTracker();
private static NextGenDB INSTANCE;
private NextGenDB(Context context) {
@@ -176,7 +179,8 @@
* double lngFrom = bb.getLonWestE6() / 1E6;
* double lngTo = bb.getLonEastE6() / 1E6;
*/
- public synchronized ArrayList<Stop> queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) throws SQLiteDatabaseLockedException {
+ @NonNull
+ public synchronized Result<ArrayList<Stop>> queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) {
ArrayList<Stop> stops = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
@@ -186,48 +190,52 @@
String minLngRaw = String.valueOf(minLng);
String maxLngRaw = String.valueOf(maxLng);
-
if(db == null) {
- return stops;
+ return Result.failure(new SQLiteException("Database is null"));
}
- try {
- 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);
+ try (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;
+ return Result.failure(e);
}catch (Exception e){
Log.e(DEBUG_TAG, "Exception occurred when getting stops");
- e.printStackTrace();
- return stops;
- }
- finally {
- db.close();
+ return Result.failure(e);
}
- return stops;
+ return Result.success(stops);
+ }
+
+ @NonNull
+ public QueryLiveData<ArrayList<Stop>> queryAllInsideMapViewLiveData(double minLat, double maxLat, double minLng, double maxLng) {
+ return new QueryLiveData<>(List.of(StopsTable.TABLE_NAME), invalidationTracker,()->{
+ Result<ArrayList<Stop>> queryResult = queryAllInsideMapView(minLat, maxLat, minLng, maxLng);
+ if(queryResult.isSuccess()){
+ return queryResult.result;
+ } else{
+ Log.w(DEBUG_TAG, "queryAllInsideMapViewLiveData failed", queryResult.exception);
+ return new ArrayList<>();
+ }
+ });
}
+
+
/**
* Query stops in the database having these IDs
- * REMEMBER TO CLOSE THE DB CONNECTION AFTERWARDS
* @param bustoDB readable database instance
* @param gtfsIDs gtfs IDs to query
* @return list of stops
*/
- public static synchronized ArrayList<Stop> queryAllStopsWithGtfsIDs(SQLiteDatabase bustoDB, List<String> gtfsIDs){
+ @NonNull
+ public static synchronized Result<ArrayList<Stop>> queryAllStopsWithGtfsIDs(@NonNull SQLiteDatabase bustoDB,@NonNull List<String> gtfsIDs){
final ArrayList<Stop> stops = new ArrayList<>();
- if(bustoDB == null){
- Log.e(DEBUG_TAG, "Asked query for IDs but database is null");
- return stops;
- } else if (gtfsIDs == null || gtfsIDs.isEmpty()) {
- return stops;
+ if (gtfsIDs.isEmpty()) {
+ return Result.success(stops);
}
final StringBuilder builder = new StringBuilder(QUERY_FROM_GTFS_ID_IN_TO_COMPLETE);
@@ -246,20 +254,90 @@
final String[] idsQuery = gtfsIDs.toArray(new String[0]);
- try {
- final Cursor result = bustoDB.query(StopsTable.TABLE_NAME,QUERY_COLUMN_stops_all, whereClause,
- idsQuery,
- null, null, null);
+ try(Cursor result = bustoDB.query(StopsTable.TABLE_NAME,QUERY_COLUMN_stops_all, whereClause,
+ idsQuery,
+ null, null, null)) {
stops.addAll(getStopsFromCursorAllFields(result));
- result.close();
} catch(SQLiteException e) {
- Log.e(DEBUG_TAG, "SQLiteException occurred");
- e.printStackTrace();
+ Log.w(DEBUG_TAG, "SQLiteException occurred in getting stops");
+ return Result.failure(e);
+ }
+ return Result.success(stops);
+ }
+ public static String buildWhereClause(String colName, List<String> args){
+ final StringBuilder builder = new StringBuilder().append(colName).append(" IN ");
+ boolean first = true;
+ builder.append(" ( ");
+ for(int i=0; i< args.size(); i++){
+ if(first){
+ first = false;
+ } else{
+ builder.append(", ");
+ }
+ builder.append("?");
}
- return stops;
+ builder.append(") ");
+ return builder.toString();
}
+ /**
+ * Query stops in the database having these IDs
+ * @param bustoDB readable database instance
+ * @param stopIds to query
+ * @return list of stops
+ */
+ @NonNull
+ public static synchronized Result<ArrayList<Stop>> queryStopsWithStopIds(@NonNull SQLiteDatabase bustoDB,@NonNull List<String> stopIds){
+ ArrayList<Stop> stops = null;
+
+ if (stopIds.isEmpty()) {
+ return Result.success(stops);
+ }
+
+ final StringBuilder builder = new StringBuilder().append(StopsTable.COL_ID).append(" IN ");
+ boolean first = true;
+ builder.append(" ( ");
+ for(int i=0; i< stopIds.size(); i++){
+ if(first){
+ first = false;
+ } else{
+ builder.append(", ");
+ }
+ builder.append("?");//.append("\"").append(id).append("\"");
+ }
+ builder.append(") ");
+ final String whereClause = builder.toString();
+ Log.d(DEBUG_TAG, "Asking for all stops with IDs, query: "+whereClause);
+
+ final String[] idsQuery = stopIds.toArray(new String[0]);
+
+ try(Cursor result = bustoDB.query(StopsTable.TABLE_NAME,QUERY_COLUMN_stops_all, whereClause,
+ idsQuery,
+ null, null, null)) {
+ stops = getStopsFromCursorAllFields(result);
+ } catch(SQLiteException e) {
+ Log.e(DEBUG_TAG, "SQLiteException occurred");
+ return Result.failure(e);
+ }
+ return Result.success(stops);
+ }
+
+ @NonNull
+ public QueryLiveData<ArrayList<Stop>> queryStopsWithStopIdsLiveData(@NonNull List<String> stopIds){
+ return new QueryLiveData<>(List.of(StopsTable.TABLE_NAME), invalidationTracker, ()->{
+ Log.d(DEBUG_TAG, "Table stops changed, redoing query");
+ SQLiteDatabase db = this.getReadableDatabase();
+ Result<ArrayList<Stop>> result = queryStopsWithStopIds(db, stopIds);
+ if(result.isSuccess()){
+ return result.result;
+ } else{
+ return null;
+ }
+ });
+ }
+
+
/**
* Get the list of stop in the query, with all the possible fields {NextGenDB.QUERY_COLUMN_stops_all}
* @param result cursor from query
@@ -307,34 +385,6 @@
return stops;
}
- public static synchronized int writeLinesStoppingHere(SQLiteDatabase db, HashMap<String,Set<String>> linesStoppingBy){
- int rowsUpdated = 0;
- for (String stopGtfsID : linesStoppingBy.keySet()){
- if (linesStoppingBy.get(stopGtfsID)==null) continue;
- if (linesStoppingBy.get(stopGtfsID).isEmpty()) continue;
- ArrayList<String> ll = new ArrayList<>(linesStoppingBy.get(stopGtfsID));
- String stringForStops = Palina.buildRoutesStringFromNames(ll);
-
- ContentValues cv = new ContentValues();
- cv.put(StopsTable.COL_LINES_STOPPING, stringForStops);
-
- // Which row to update, based on the title
- String selection = StopsTable.COL_GTFS_ID + " LIKE ?";
- String[] selectionArgs = { stopGtfsID };
-
- int count = db.update(
- StopsTable.TABLE_NAME,
- cv,
- selection,
- selectionArgs);
- if (count > 1){
- Log.e(DEBUG_TAG, "Updated the linesStoppingBy for more than one stop");
- }
- rowsUpdated += count;
- }
- return rowsUpdated;
- }
-
public static boolean insertBranchesIntoDB(@NonNull Context context, @NonNull List<Route> routesToInsert){
final NextGenDB nextGenDB = NextGenDB.getInstance(context);
//ContentValues[] values = new ContentValues[routesToInsert.size()];
@@ -345,40 +395,7 @@
//if it has received an interrupt, stop
if(Thread.interrupted()) return false;
//otherwise, build contentValues
- final ContentValues cv = new ContentValues();
- cv.put(BranchesTable.COL_BRANCHID,r.branchid);
- cv.put(LinesTable.COLUMN_NAME,r.getName());
- cv.put(BranchesTable.COL_DIRECTION,r.destinazione);
- cv.put(BranchesTable.COL_DESCRIPTION,r.description);
- for (int day :r.serviceDays) {
- switch (day){
- case Calendar.MONDAY:
- cv.put(BranchesTable.COL_LUN,1);
- break;
- case Calendar.TUESDAY:
- cv.put(BranchesTable.COL_MAR,1);
- break;
- case Calendar.WEDNESDAY:
- cv.put(BranchesTable.COL_MER,1);
- break;
- case Calendar.THURSDAY:
- cv.put(BranchesTable.COL_GIO,1);
- break;
- case Calendar.FRIDAY:
- cv.put(BranchesTable.COL_VEN,1);
- break;
- case Calendar.SATURDAY:
- cv.put(BranchesTable.COL_SAB,1);
- break;
- case Calendar.SUNDAY:
- cv.put(BranchesTable.COL_DOM,1);
- break;
- }
- }
- if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
- cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
-
- //values[routesToInsert.indexOf(r)] = cv;
+ final ContentValues cv = createContentValuesBranch(r);
branchesValues.add(cv);
if(r.getStopsList() != null)
for(int i=0; i<r.getStopsList().size();i++){
@@ -392,29 +409,134 @@
connectionsVals.add(connVal);
}
}
+ //ContentResolver cr = context.getContentResolver();
+
starttime = System.currentTimeMillis();
- ContentResolver cr = context.getContentResolver();
- try {
- cr.bulkInsert(Uri.parse("content://" + AppDataProvider.AUTHORITY + "/branches/"), branchesValues.toArray(new ContentValues[0]));
- endtime = System.currentTimeMillis();
- Log.d("DataDownload", "Inserted branches, took " + (endtime - starttime) + " ms");
- } catch (SQLException exc){
- Log.e("AsyncDataDownload","Inserting data: some error happened, aborting the database insert");
- exc.printStackTrace();
+ //cr.bulkInsert(Uri.parse("content://" + AppDataProvider.AUTHORITY + "/branches/"), branchesValues.toArray(new ContentValues[0]));
+ int rows = nextGenDB.insertBatchContent(branchesValues.toArray(new ContentValues[0]), BranchesTable.TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE);
+ endtime = System.currentTimeMillis();
+ Log.d("DataDownload", "Inserted branches, took " + (endtime - starttime) + " ms, inserted "+ rows + " rows");
+ if(rows == RESULT_SQLITE_ERROR){
return false;
}
+
if (!connectionsVals.isEmpty()) {
starttime = System.currentTimeMillis();
ContentValues[] valArr = connectionsVals.toArray(new ContentValues[0]);
Log.d("DataDownloadInsert", "inserting " + valArr.length + " connections");
- int rows = nextGenDB.insertBatchContent(valArr, ConnectionsTable.TABLE_NAME);
+ rows = nextGenDB.insertBatchContent(valArr, ConnectionsTable.TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE);
endtime = System.currentTimeMillis();
Log.d("DataDownload", "Inserted connections found, took " + (endtime - starttime) + " ms, inserted " + rows + " rows");
+ if(rows == RESULT_SQLITE_ERROR){
+ return false;
+ }
}
//nextGenDB.close();
return true;
}
+
+ public boolean updateDataStops(List<Palina> palinas, HashMap<String, Set<String>> routesStoppingByStop){
+ SQLiteDatabase db = getWritableDatabase();
+ boolean completed = false;
+ if(!db.isOpen()){
+ //catch errors like: java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
+ //we have to abort the work and restart it
+ return completed;
+ }
+ int patternsStopsHits = 0;
+ long startTime = System.currentTimeMillis();
+
+ try {
+ //TODO: Get the type of stop from the lines
+ //Empty the needed tables
+
+ db.beginTransaction();
+ //put new data
+
+ Log.d(DEBUG_TAG, "Inserting " + palinas.size() + " stops");
+ String routesStoppingString = "";
+
+ for (final Palina p : palinas) {
+ final ContentValues cv = new ContentValues();
+
+ cv.put(StopsTable.COL_ID, p.ID);
+ cv.put(StopsTable.COL_NAME, p.getStopDefaultName());
+ if (p.location != null)
+ cv.put(StopsTable.COL_LOCATION, p.location);
+ cv.put(StopsTable.COL_LAT, p.getLatitude());
+ cv.put(StopsTable.COL_LONG, p.getLongitude());
+ if (p.getAbsurdGTTPlaceName() != null)
+ cv.put(StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
+ if (p.gtfsID != null && routesStoppingByStop.containsKey(p.gtfsID)) {
+ final ArrayList<String> routesSs = new ArrayList<>(routesStoppingByStop.get(p.gtfsID));
+ routesStoppingString = Palina.buildRoutesStringFromNames(routesSs);
+ patternsStopsHits++;
+ } else {
+ routesStoppingString = p.routesThatStopHereToString();
+ }
+ cv.put(StopsTable.COL_LINES_STOPPING, routesStoppingString);
+ if (p.type != null) cv.put(StopsTable.COL_TYPE, p.type.getCode());
+ if (p.gtfsID != null) cv.put(StopsTable.COL_GTFS_ID, p.gtfsID);
+ //Log.d(DEBUG_TAG,cv.toString());
+ //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
+ //valuesArr[i] = cv;
+ db.replace(StopsTable.TABLE_NAME, null, cv);
+
+ }
+ db.setTransactionSuccessful();
+ completed = true;
+ }catch (SQLException exc){
+ Log.w(DEBUG_TAG, "SQlite Exception: " + exc.getMessage());
+ } finally {
+ db.endTransaction();
+ }
+
+ long endTime = System.currentTimeMillis();
+ Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s, successful: "+completed);
+ Log.d(DEBUG_TAG, "\t"+patternsStopsHits+" routes string were built from the patterns");
+ if(completed){
+ invalidationTracker.notifyInvalidation(StopsTable.TABLE_NAME);
+ }
+ return completed;
+ }
+
+ @NonNull
+ private static ContentValues createContentValuesBranch(Route r) {
+ final ContentValues cv = new ContentValues();
+ cv.put(BranchesTable.COL_BRANCHID, r.branchid);
+ cv.put(LinesTable.COLUMN_NAME, r.getName());
+ cv.put(BranchesTable.COL_DIRECTION, r.destinazione);
+ cv.put(BranchesTable.COL_DESCRIPTION, r.description);
+ for (int day : r.serviceDays) {
+ switch (day){
+ case Calendar.MONDAY:
+ cv.put(BranchesTable.COL_LUN,1);
+ break;
+ case Calendar.TUESDAY:
+ cv.put(BranchesTable.COL_MAR,1);
+ break;
+ case Calendar.WEDNESDAY:
+ cv.put(BranchesTable.COL_MER,1);
+ break;
+ case Calendar.THURSDAY:
+ cv.put(BranchesTable.COL_GIO,1);
+ break;
+ case Calendar.FRIDAY:
+ cv.put(BranchesTable.COL_VEN,1);
+ break;
+ case Calendar.SATURDAY:
+ cv.put(BranchesTable.COL_SAB,1);
+ break;
+ case Calendar.SUNDAY:
+ cv.put(BranchesTable.COL_DOM,1);
+ break;
+ }
+ }
+ if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
+ cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
+ return cv;
+ }
/*
static ArrayList<Stop> createStopListFromCursor(Cursor data){
ArrayList<Stop> stopList = new ArrayList<>();
@@ -448,33 +570,29 @@
* @param content ContentValues array
* @return number of lines inserted
*/
- public int insertBatchContent(ContentValues[] content,String tableName) throws SQLiteException {
+ public int insertBatchContent(ContentValues[] content,String tableName, int sqliteConflictStrategy) {
final SQLiteDatabase db = this.getWritableDatabase();
int success = 0;
-
- db.beginTransaction();
-
- for (final ContentValues cv : content) {
- try {
- db.replaceOrThrow(tableName, null, cv);
- success++;
- } catch (SQLiteConstraintException d){
- Log.w("NextGenDB_Insert","Failed insert with FOREIGN KEY... \n"+d.getMessage());
-
- } catch (Exception e) {
- Log.w("NextGenDB_Insert", e);
+ try{
+ db.beginTransaction();
+ for(ContentValues cv:content){
+ db.insertWithOnConflict(tableName, null, cv, sqliteConflictStrategy);
}
+ db.setTransactionSuccessful();
+ success = content.length;
+ } catch (SQLException e) {
+ Log.w(DEBUG_TAG, "Error inserting batch content into table "+tableName, e);
+ success = RESULT_SQLITE_ERROR;
+ } finally {
+ db.endTransaction();
+ }
+ if (success > 0) {
+ invalidationTracker.notifyInvalidation(tableName); // ✅ Notify only after confirmed commit
}
- db.setTransactionSuccessful();
- db.endTransaction();
return success;
}
- int updateLinesStoppingInStop(List<Stop> stops){
- return 0;
- }
-
public static List<String> splitLinesString(String linesStr){
return Arrays.asList(linesStr.split("\\s*,\\s*"));
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
@@ -18,34 +18,73 @@
package it.reyboz.bustorino.data
import android.content.Context
+import android.util.Log
import androidx.sqlite.SQLiteException
import it.reyboz.bustorino.backend.Result
import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.StopFavoritesData
import it.reyboz.bustorino.backend.utils
import java.util.ArrayList
import java.util.concurrent.Executor
class OldDataRepository(private val executor: Executor,
private val nextGenDB: NextGenDB,
+ private val userDB: UserDB
) {
- constructor(executor: Executor, context: Context): this(executor, NextGenDB.getInstance(context))
+ constructor(executor: Executor, context: Context): this(executor, NextGenDB.getInstance(context), UserDB.getInstance(context))
fun requestStopsWithGtfsIDs(
- gtfsIDs: List<String?>?,
- callback: Callback<List<Stop>>
+ gtfsIDs: List<String>,
+ callback: Callback<ArrayList<Stop>>
) {
+ executor.execute {
+ //final NextGenDB dbHelper = new NextGenDB(context);
+ val db = nextGenDB.readableDatabase
+ val stopResult= NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs)
+ //Result<List<Stop>> result = Result.success;
+ callback.onComplete(stopResult)
+ }
+ }
+ fun requestStopsWithIds(ids: List<String>, callback: Callback<ArrayList<Stop>>) {
executor.execute {
try {
//final NextGenDB dbHelper = new NextGenDB(context);
val db = nextGenDB.readableDatabase
- val stops: List<Stop> = NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs)
+ val stopsResult= NextGenDB.queryStopsWithStopIds(db, ids)
//Result<List<Stop>> result = Result.success;
- callback.onComplete(Result.success(stops))
+ callback.onComplete(stopsResult);
+ } catch (e: Exception) {
+ callback.onComplete(Result.failure(e))
+ }
+ }
+
+ }
+
+ fun getFavoritesData(ids: List<String>, callback: Callback<ArrayList<StopFavoritesData>>){
+ executor.execute {
+ try {
+ val data =userDB.queryDataForStopIds(ids)
+ Log.d(DEBUG_TAG, "received favorites data: $data")
+ if(data != null){
+ val res = Result.success(data)
+ callback.onComplete(res)
+ }
+ else{
+ callback.onComplete(Result.failure(android.database.sqlite.SQLiteException()))
+ }
} catch (e: Exception) {
callback.onComplete(Result.failure(e))
}
}
}
+ fun getFavoritesLiveData(): QueryLiveData<List<StopFavoritesData>> {
+ return userDB.favoritesLiveData
+ }
+ fun getFavoritesLiveDataByStopId(ids: List<String>) = userDB.getLiveDataForStopIds(ids)
+
+ fun getStopsForIdsLiveData(ids: List<String>): QueryLiveData<ArrayList<Stop>> {
+ return nextGenDB.queryStopsWithStopIdsLiveData(ids)
+ }
fun requestStopsInArea(
latitFrom: Double,
@@ -56,21 +95,23 @@
){
//Log.d(DEBUG_TAG, "Async Stop Fetcher started working");
executor.execute {
- var result = ArrayList<Stop>()
- try {
- result = nextGenDB.queryAllInsideMapView(
+ //var result = ArrayList<Stop>()
+ callback.onComplete(nextGenDB.queryAllInsideMapView(
latitFrom, latitTo,
longitFrom, longitTo
- )
- } catch (e: SQLiteException){
- callback.onComplete(Result.failure(e))
- }
+ ))
- callback.onComplete(Result.success(result))
}
}
+ fun requestStopsInAreaLiveData(minLat: Double,
+ maxLat: Double,
+ minLong: Double,
+ maxLong: Double): QueryLiveData<ArrayList<Stop>> {
+ return nextGenDB.queryAllInsideMapViewLiveData(minLat, maxLat, minLong, maxLong)
+ }
+
/**
* Request all the stops in position [latitude], [longitude], in the "square" with radius [distanceMeters]
* Returns nothing, [callback] will be called if the query succeeds
@@ -89,4 +130,8 @@
fun interface Callback<T> {
fun onComplete(result: Result<T>)
}
+
+ companion object {
+ private const val DEBUG_TAG = "BusTO-OldDataRepo"
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/QueryLiveData.kt b/app/src/main/java/it/reyboz/bustorino/data/QueryLiveData.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/data/QueryLiveData.kt
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.data
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.launch
+
+/**
+ * Class to observe the result of queries from database
+ */
+class QueryLiveData<T>(
+ private val tablesToObserve: List<String>,
+ private val tracker: InvalidationTracker,
+ private val queryRunner: () -> T
+) : LiveData<T>() {
+
+ private val liveDataScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+
+ private val invalidationCallback = InvalidationTracker.Observer { fetchData() }
+
+ override fun onActive() {
+ tablesToObserve.forEach {
+ tracker.addObserver(it, invalidationCallback)
+ }
+ fetchData()
+ }
+
+ override fun onInactive() {
+ tablesToObserve.forEach {
+ tracker.removeObserver(it, invalidationCallback)
+ }
+ liveDataScope.coroutineContext.cancelChildren() // Cancel any in-flight queries
+ }
+
+ private fun fetchData() {
+ liveDataScope.coroutineContext.cancelChildren()
+ liveDataScope.launch {
+ val newValue = queryRunner()
+ if(newValue == null){
+ Log.w("BusTO-QueryLiveData", "Attempting to post value but it is null, tables $tablesToObserve")
+ }
+ postValue(newValue) // postValue is safe to call from a background thread
+ }
+ }
+}
\ No newline at end of file
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
@@ -28,15 +28,16 @@
import android.net.Uri;
import android.util.Log;
-import java.io.IOException;
import java.util.*;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.siegmar.fastcsv.reader.CloseableIterator;
import de.siegmar.fastcsv.reader.CsvReader;
import de.siegmar.fastcsv.reader.CsvRecord;
import de.siegmar.fastcsv.writer.CsvWriter;
import it.reyboz.bustorino.backend.Stop;
+import it.reyboz.bustorino.backend.StopFavoritesData;
import it.reyboz.bustorino.backend.StopsDBInterface;
public class UserDB extends SQLiteOpenHelper {
@@ -48,8 +49,11 @@
public final static String COL_USERNAME="username";
public static final int FILE_INVALID=-10;
+ private static final String DEBUG_TAG = "BusTO-FavoritesUserDB";
private final static String[] usernameColumnNameAsArray = {"username"};
- public final static String[] getFavoritesColumnNamesAsArray = {COL_ID, COL_USERNAME};
+ public final static String[] FAVORITES_COLUMNS_ARRAY = {COL_ID, COL_USERNAME};
+
+ private final InvalidationTracker invalidationTracker = new InvalidationTracker();
private static final Uri FAVORITES_URI = AppDataProvider.getUriBuilderToComplete().appendPath(
AppDataProvider.FAVORITES).build();
@@ -169,12 +173,13 @@
/**
* Check if a stop ID is in the favorites
- *
- * @param db readable database
+ **
* @param stopId stop ID
* @return boolean
*/
- public static boolean isStopInFavorites(SQLiteDatabase db, String stopId) {
+ public boolean isStopInFavorites(String stopId) {
+
+ SQLiteDatabase db = this.getReadableDatabase();
boolean found = false;
try {
@@ -193,43 +198,45 @@
/**
* Gets stop name set by the user.
*
- * @param db readable database
* @param stopID stop ID
* @return name set by user, or null if not set\not found
*/
- public static @Nullable String getStopUserName(SQLiteDatabase db, String stopID) {
+ private @Nullable String getStopUserName(SQLiteDatabase db,String stopID) {
String username = null;
- try {
- Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopID}, null, null, null);
+ try(Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?",
+ new String[] {stopID}, null, null, null)) {
if(c.moveToNext()) {
int userNameIndex = c.getColumnIndex("username");
if (userNameIndex>=0)
username = c.getString(userNameIndex);
}
- c.close();
} catch(SQLiteException e) {
Log.e("BusTO-UserDB","Cannot get stop User name for stop "+stopID+":\n"+e);
}
return username;
}
+ public @Nullable String getStopUserName(String stopID) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ return getStopUserName(db,stopID);
+ }
/**
* Get all the bus stops marked as favorites
*
- * @param db
* @param dbi
* @return
*/
- public static List<Stop> getFavorites(SQLiteDatabase db, StopsDBInterface dbi) {
+ public List<Stop> getFavorite( StopsDBInterface dbi) {
+ SQLiteDatabase db = this.getReadableDatabase();
List<Stop> l = new ArrayList<>();
Stop s;
String stopID, stopUserName;
try {
- Cursor c = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray, null, null, null, null, null, null);
+ Cursor c = db.query(TABLE_NAME, FAVORITES_COLUMNS_ARRAY, null, null, null, null, null, null);
int colID = c.getColumnIndex("ID");
int colUser = c.getColumnIndex("username");
@@ -264,7 +271,7 @@
public static ArrayList<Stop> getFavoritesFromCursor(Cursor cursor, String[] columns){
List<String> colsList = Arrays.asList(columns);
- if (!colsList.contains(getFavoritesColumnNamesAsArray[0]) || !colsList.contains(getFavoritesColumnNamesAsArray[1])){
+ if (!colsList.contains(FAVORITES_COLUMNS_ARRAY[0]) || !colsList.contains(FAVORITES_COLUMNS_ARRAY[1])){
throw new IllegalArgumentException();
}
ArrayList<Stop> l = new ArrayList<>();
@@ -286,55 +293,162 @@
}
- public static boolean addOrUpdateStop(Stop s, SQLiteDatabase db) {
+ @NonNull
+ public ArrayList<StopFavoritesData> getAllFavoritesData(){
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, FAVORITES_COLUMNS_ARRAY, null, null, null, null, null);
+
+ ArrayList<StopFavoritesData> l = new ArrayList<>();
+ final int colID = cursor.getColumnIndex("ID");
+ final int colUser = cursor.getColumnIndex("username");
+ while(cursor.moveToNext()) {
+ final String stopUserName = cursor.getString(colUser);
+ final String stopID = cursor.getString(colID);
+
+ l.add(new StopFavoritesData(stopID, stopUserName));
+ }
+ cursor.close();
+ return l;
+
+ }
+ @Nullable
+ public ArrayList<StopFavoritesData> queryDataForStopIds(List<String> stopIds) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ ArrayList<StopFavoritesData> result = null;
+
+ final String whereClause = NextGenDB.buildWhereClause(COL_ID, stopIds);
+ final String[] whereArgs = stopIds.toArray(new String[0]);
+ Log.d(DEBUG_TAG, "queryDtaForStopId: " + whereClause+ " args: " + Arrays.toString(whereArgs));
+ try(Cursor c = db.query(
+ TABLE_NAME, FAVORITES_COLUMNS_ARRAY, whereClause,
+ whereArgs, null, null, null, null)){
+
+ result = getFavoritesDataFromCursor(c, FAVORITES_COLUMNS_ARRAY);
+ }
+ catch(SQLiteException e) {
+ Log.e(DEBUG_TAG, "queryDataForStopIds favorites failed for " + stopIds, e);
+ return null;
+ }
+
+ return result;
+ }
+
+ @NonNull
+ public QueryLiveData<List<StopFavoritesData>> getLiveDataForStopIds(List<String> stopIds) {
+ return new QueryLiveData<>(List.of(TABLE_NAME), invalidationTracker, () -> {
+ Log.d(DEBUG_TAG, "Favorites table changed, redoing query");
+ return queryDataForStopIds(stopIds);
+ });
+ }
+ @NonNull
+ public QueryLiveData<List<StopFavoritesData>> getFavoritesLiveData() {
+ return new QueryLiveData<>(List.of(TABLE_NAME), invalidationTracker, () -> {
+ Log.d(DEBUG_TAG, "Favorites table changed, redoing query");
+ return getAllFavoritesData();
+ });
+ }
+
+
+ @NonNull
+ public static ArrayList<StopFavoritesData> getFavoritesDataFromCursor(@NonNull Cursor cursor, String[] columns){
+ List<String> colsList = Arrays.asList(columns);
+ if (!colsList.contains(FAVORITES_COLUMNS_ARRAY[0]) || !colsList.contains(FAVORITES_COLUMNS_ARRAY[1])){
+ throw new IllegalArgumentException();
+ }
+ ArrayList<StopFavoritesData> l = new ArrayList<>();
+ final int colID = cursor.getColumnIndex("ID");
+ final int colUser = cursor.getColumnIndex("username");
+ while(cursor.moveToNext()) {
+ final String stopUserName = cursor.getString(colUser);
+ final String stopID = cursor.getString(colID);
+ l.add(new StopFavoritesData(stopID, stopUserName));
+ }
+ return l;
+
+ }
+ public boolean addOrUpdateStop(Stop s) {
+ return addOrUpdateStop(s.ID, s.getStopUserName());
+ }
+ public boolean addOrUpdateStop(@NonNull String stopID, @Nullable String stopUserName) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ return addOrUpdateStop(stopID, stopUserName, db);
+ }
+ private boolean addOrUpdateStop(@NonNull String stopID, @Nullable String stopUserName, SQLiteDatabase db) {
ContentValues cv = new ContentValues();
long result = -1;
- String un = s.getStopUserName();
- cv.put("ID", s.ID);
+ cv.put("ID", stopID);
// is there an username?
- if(un == null) {
+ if(stopUserName == null) {
// no: see if it's in the database
- cv.put("username", getStopUserName(db, s.ID));
+ cv.put("username", getStopUserName(db,stopID));
} else {
// yes: use it
- cv.put("username", un);
+ cv.put("username", stopUserName);
}
try {
//ignore and throw -1 if the row is already in the DB
result = db.insertWithOnConflict(TABLE_NAME, null, cv,SQLiteDatabase.CONFLICT_IGNORE);
- } catch (SQLiteException ignored) {}
-
+ } catch (SQLiteException ignored) {
+ Log.e(DEBUG_TAG, "cannot insert stop in user db, error: " + ignored);
+ }
+ if(result!=-1)
+ invalidationTracker.notifyInvalidation(TABLE_NAME);
// Android Studio suggested this unreadable replacement: return true if insert succeeded (!= -1), or try to update and return
- return (result != -1) || updateStop(s, db);
+ return (result != -1) || (updateStop(stopID,stopUserName, db));
+ }
+ private boolean addOrUpdateStop(@NonNull Stop s, SQLiteDatabase db) {
+ return addOrUpdateStop(s.ID, s.getStopUserName(), db);
}
- public static boolean updateStop(Stop s, SQLiteDatabase db) {
+ private boolean updateStop(@NonNull String stopID, @Nullable String stopUsername, @NonNull SQLiteDatabase db) {
try {
ContentValues cv = new ContentValues();
- cv.put("username", s.getStopUserName());
- db.update(TABLE_NAME, cv, "ID = ?", new String[]{s.ID});
+ cv.put("username", stopUsername);
+ db.update(TABLE_NAME, cv, "ID = ?", new String[]{stopID});
+ invalidationTracker.notifyInvalidation(TABLE_NAME);
+
return true;
} catch(SQLiteException e) {
+ Log.w(DEBUG_TAG, "setStopUsername failed",e);
return false;
}
}
+ public boolean updateStop(@NonNull Stop s) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ return updateStop(s.ID, s.getStopUserName(), db);
+ }
- public static boolean deleteStop(Stop s, SQLiteDatabase db) {
+ private boolean deleteStop(@NonNull String stopID,@NonNull SQLiteDatabase db) {
try {
- db.delete(TABLE_NAME, "ID = ?", new String[]{s.ID});
+ db.delete(TABLE_NAME, "ID = ?", new String[]{stopID});
+ invalidationTracker.notifyInvalidation(TABLE_NAME);
return true;
} catch(SQLiteException e) {
+ Log.w(DEBUG_TAG, "failed to remove stop, ID: "+stopID);
return false;
}
}
- public static boolean checkStopInFavorites(String stopID, Context con){
+ private boolean deleteStop(@NonNull Stop s, @NonNull SQLiteDatabase db) {
+ return deleteStop(s.ID, db);
+ }
+ public boolean deleteStop(@NonNull String stopID) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ return deleteStop(stopID, db);
+ }
+ public boolean deleteStop(@NonNull Stop s) {
+ return deleteStop(s.ID);
+ }
+
+
+
+ public boolean checkStopInFavorites(String stopID, Context con){
boolean found = false;
// no stop no party
if (stopID != null) {
- SQLiteDatabase userDB = new UserDB(con).getReadableDatabase();
- found = UserDB.isStopInFavorites(userDB, stopID);
+ UserDB userDB = UserDB.getInstance(con);
+ found = userDB.isStopInFavorites(stopID);
}
return found;
@@ -346,7 +460,7 @@
String sortOrder =
COL_ID + " DESC";
- Cursor cursor = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray,null,null,null,null, sortOrder);
+ Cursor cursor = db.query(TABLE_NAME, FAVORITES_COLUMNS_ARRAY,null,null,null,null, sortOrder);
final int nCols = 2;//cursor.getColumnCount();
writer.writeRecord(cursor.getColumnNames());
@@ -401,4 +515,6 @@
return updated;
}
+
+ //TODO: Copy method from @AppDataProvider to get all the favorites
}
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
@@ -44,7 +44,7 @@
import it.reyboz.bustorino.data.AppDataProvider
import it.reyboz.bustorino.data.NextGenDB
import it.reyboz.bustorino.data.UserDB
-import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction
+import it.reyboz.bustorino.middleware.CoroutineFavoriteAction
import it.reyboz.bustorino.util.LinesNameSorter
import it.reyboz.bustorino.viewmodels.ArrivalsViewModel
import java.util.*
@@ -61,7 +61,6 @@
private var lastUpdatedPalina: Palina? = null
private var needUpdateOnAttach = false
private var fetchersChangeRequestPending = false
- private var stopIsInFavorites = false
//Views
protected lateinit var addToFavorites: ImageButton
@@ -133,6 +132,8 @@
stopID = requireArguments().getString(KEY_STOP_ID) ?: ""
DEBUG_TAG = DEBUG_TAG_ALL + " " + stopID
+ arrivalsViewModel.setStopId(stopID)
+
//this might really be null
stopName = requireArguments().getString(KEY_STOP_NAME)
val arrivalsFragment = this
@@ -210,7 +211,7 @@
addToFavorites.setClickable(true)
addToFavorites.setOnClickListener(View.OnClickListener { v: View? ->
// add/remove the stop in the favorites
- toggleLastStopToFavorites()
+ toggleStopFavorites()
})
val displayName = requireArguments().getString(STOP_TITLE)
@@ -245,9 +246,10 @@
timesSourceTextView.setText(sourcesTextViewData);
}*/
//need to do this when we recreate the fragment but we haven't updated the arrival times
- if(lastUpdatedPalina == null && arrivalsViewModel.palinaLiveData.value != null) {
+ val tentPalina = arrivalsViewModel.palinaToShow.value
+ if(lastUpdatedPalina == null && tentPalina != null) {
//this updates lastUpdatedPalina and also shows the arrival source
- updateFragmentData(arrivalsViewModel.palinaLiveData.value!!)
+ updateFragmentData(tentPalina)
}
//lastUpdatedPalina?.let { showArrivalsSources(it) }
@@ -272,7 +274,7 @@
}
})
- arrivalsViewModel.palinaLiveData.observe(viewLifecycleOwner){
+ arrivalsViewModel.palinaToShow.observe(viewLifecycleOwner){
Log.d(DEBUG_TAG, "New result palina observed, has coords: ${it.hasCoords()}, title ${it?.stopDisplayName}, number of passages: ${it.totalNumberOfPassages}")
val palinaIsValid = it!=null && (it.totalNumberOfPassages>0 || it.stopDisplayName!=null)
@@ -319,6 +321,9 @@
else -> showFetcherMessage(R.string.internal_error, src)
}
}
+ arrivalsViewModel.stopInFavorites.observe(viewLifecycleOwner, { isFavorite ->
+ updateStarIcon(isFavorite)
+ })
return root
}
@@ -609,7 +614,7 @@
when (id) {
loaderFavId -> {
builder.appendPath("favorites").appendPath(stopID)
- cl = CursorLoader(requireContext(), builder.build(), UserDB.getFavoritesColumnNamesAsArray, null, null, null)
+ cl = CursorLoader(requireContext(), builder.build(), UserDB.FAVORITES_COLUMNS_ARRAY, null, null, null)
}
loaderStopId -> {
@@ -630,9 +635,10 @@
}
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
+ /*
when (loader.id) {
loaderFavId -> {
- val colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1])
+ val colUserName = data.getColumnIndex(UserDB.FAVORITES_COLUMNS_ARRAY[1])
if (data.count > 0) {
// IT'S IN FAVORITES
data.moveToFirst()
@@ -646,7 +652,7 @@
stopIsInFavorites = false
}
updateStarIcon()
- /*
+
if (stopName == null) {
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment$tag", "Stop wasn't in the favorites and has no name, looking in the DB")
@@ -655,10 +661,8 @@
arguments, this
)
}
- 6
- */
}
- /*
+
loaderStopId -> if (data.count > 0) {
data.moveToFirst()
val index = data.getColumnIndex(
@@ -673,10 +677,14 @@
Log.w("ArrivalsFragment$tag", "Stop is not inside the database... CLOISTER BELL")
}
- */
+
}
+
+ */
}
+
+
override fun onLoaderReset(loader: Loader<Cursor>) {
//NOTHING TO DO
}
@@ -687,20 +695,22 @@
arrivalsRecyclerView.visibility = View.VISIBLE
}
- fun toggleLastStopToFavorites() {
+ fun toggleStopFavorites() {
val stop: Stop? = lastUpdatedPalina
if (stop != null) {
// toggle the status in background
+ CoroutineFavoriteAction(requireContext().applicationContext, CoroutineFavoriteAction.Action.TOGGLE){
- AsyncStopFavoriteAction(
- requireContext().applicationContext, AsyncStopFavoriteAction.Action.TOGGLE
- ) { v: Boolean -> updateStarIconFromLastBusStop(v) }.execute(stop)
- } else {
+ }.execute(stop)
+
+
+ } else {
// this case have no sense, but just immediately update the favorite icon
- updateStarIconFromLastBusStop(true)
+ //updateStarIconFromLastBusStop(true)
+ Log.d(DEBUG_TAG, "Stop is null!")
}
}
-
+ /*
/**
* Update the star "Add to favorite" icon
*/
@@ -709,28 +719,14 @@
else toggleDone
updateStarIcon()
-
- // check if there is a last Stop
- /*
- if (stopID == null) {
- addToFavorites.setVisibility(View.INVISIBLE);
- } else {
- // filled or outline?
- if (isStopInFavorites(stopID)) {
- addToFavorites.setImageResource(R.drawable.ic_star_filled);
- } else {
- addToFavorites.setImageResource(R.drawable.ic_star_outline);
- }
-
- addToFavorites.setVisibility(View.VISIBLE);
- }
- */
}
+ */
+
/**
* Update the star icon according to `stopIsInFavorites`
*/
- fun updateStarIcon() {
+ fun updateStarIcon(stopIsInFavorites: Boolean) {
// no favorites no party!
// check if there is a last Stop
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
@@ -192,6 +192,7 @@
val csvWriter = CsvWriter.builder().build(outWriter)
val userDB = UserDB.getInstance(context)
userDB.writeFavoritesToCsv(csvWriter)
+ csvWriter.flush()
outWriter.flush()
zipOutputStream.closeEntry()
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/FavoritesFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/FavoritesFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/FavoritesFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/FavoritesFragment.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -50,8 +49,8 @@
import it.reyboz.bustorino.adapters.StopRecyclerAdapter;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.DatabaseUpdate;
-import it.reyboz.bustorino.data.FavoritesViewModel;
-import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
+import it.reyboz.bustorino.middleware.CoroutineFavoriteAction;
+import it.reyboz.bustorino.viewmodels.FavoritesViewModel;
public class FavoritesFragment extends ScreenBaseFragment {
@@ -140,7 +139,7 @@
registerForContextMenu(favoriteRecyclerView);
- model.getFavorites().observe(getViewLifecycleOwner(), this::showStops);
+ model.getFavoritesWithStop().observe(getViewLifecycleOwner(), this::showStops);
// watch the DB update
DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, workInfos -> {
@@ -153,7 +152,7 @@
//force reload if it was previously running
if(model!=null && dbUpdateRunning) {
Log.d(DEBUG_TAG,"DB Finished updating, reload favorites");
- model.getFavorites().forceReload();
+ //model.getFavorites().forceReload();
}
dbUpdateRunning = false;
}
@@ -219,11 +218,9 @@
switch (item.getItemId()) {
case R.id.action_favourite_entry_delete:
if (getContext()!=null)
- new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE,
- result -> {
-
- }).execute(busStop);
-
+ new CoroutineFavoriteAction(requireContext().getApplicationContext(), CoroutineFavoriteAction.Action.REMOVE,
+ result -> {}
+ ).execute(busStop);
return true;
case R.id.action_rename_bus_stop_username:
@@ -325,10 +322,17 @@
private void launchUpdate(Stop busStop){
if (getContext()!=null)
- new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.UPDATE,
+
+ new CoroutineFavoriteAction(requireContext().getApplicationContext(), CoroutineFavoriteAction.Action.UPDATE,
+ result -> {
+ //Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show();
+ }).execute(busStop);
+ /*new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.UPDATE,
result -> {
//Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show();
}).execute(busStop);
+ */
+
}
/*
THIS LOOKS TERRIBLE
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
@@ -106,7 +106,7 @@
protected var fragmentListener: CommonFragmentListener? = null
// Declare a variable for MapView
- protected lateinit var mapView: MapView
+ protected var mapView: MapView? = null
protected lateinit var mapStyle: Style
protected lateinit var stopsSource: GeoJsonSource
protected lateinit var busesSource: GeoJsonSource
@@ -120,7 +120,7 @@
protected lateinit var locationProvider: FusedNativeLocationProvider
protected var shownToastNoPosition = false
- private var locationEnabledOnDevice = true
+ protected var locationEnabledOnDevice = true
//TODO ACTIVATE THIS
private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener(){ pref, key ->
@@ -271,8 +271,8 @@
}
override fun onResume() {
- mapView.onResume()
super.onResume()
+ mapView?.onResume()
val newMapStyle = PreferencesHolder.getMapLibreStyleFile(requireContext())
Log.d(DEBUG_TAG, "onResume newMapStyle: $newMapStyle, lastMapStyle: $lastMapStyle")
if(newMapStyle!=lastMapStyle){
@@ -280,32 +280,38 @@
}
}
-
-
- @Deprecated("Deprecated in Java")
override fun onLowMemory() {
- mapView.onLowMemory()
+ mapView?.onLowMemory()
super.onLowMemory()
}
override fun onStart() {
super.onStart()
- mapView.onStart()
+ mapView?.onStart()
}
override fun onDestroy() {
- mapView.onDestroy()
+ mapView?.onDestroy()
Log.d(DEBUG_TAG, "Destroyed mapView Fragment!!")
super.onDestroy()
}
+ override fun onStop() {
+ mapView?.onStop()
+ super.onStop()
+ }
+
override fun onPause() {
+ mapView?.onPause()
super.onPause()
}
+
override fun onDestroyView() {
bottomLayout = null
locationProvider.removeListener(deviceLocationStatusListener)
+ mapInitialized = false
+ locationInitialized = false
super.onDestroyView()
}
@@ -405,7 +411,7 @@
}
protected fun initSymbolManager(mapReady: MapLibreMap , style: Style){
- val sm = SymbolManager(mapView, mapReady, style)
+ val sm = SymbolManager(mapView!!, mapReady, style)
sm.iconAllowOverlap = true
sm.textAllowOverlap = false
sm.addClickListener { _ ->
@@ -1086,6 +1092,8 @@
}
+
+
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
@@ -234,7 +234,7 @@
//map stuff
mapView = rootView.findViewById(R.id.lineMap)
- mapView.getMapAsync(this)
+ mapView!!.getMapAsync(this)
// Setup close button
@@ -267,7 +267,7 @@
initializeRecyclerView()
switchButton.setOnClickListener{
- if(mapView.visibility == View.VISIBLE){
+ if(mapView?.visibility == View.VISIBLE){
hideMapAndShowStopList()
} else{
hideStopListAndShowMap()
@@ -328,7 +328,7 @@
Log.w(DEBUG_TAG, "The selectedPattern is null!")
return@observe
}
- if(mapView.visibility ==View.VISIBLE) {
+ if(mapView?.visibility ==View.VISIBLE) {
// 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
@@ -383,7 +383,7 @@
Log.d(DEBUG_TAG, "request stops for pattern ${patternWithStops.pattern.code}")
setPatternAndReqStops(patternWithStops)
- if(mapView.visibility == View.VISIBLE) {
+ if(mapView?.visibility == View.VISIBLE) {
//Clear buses if we are changing direction
currentShownPattern?.let { patt ->
if(patt.directionId != patternWithStops.pattern.directionId){
@@ -425,7 +425,7 @@
// ------------- UI switch stuff ---------
private fun hideMapAndShowStopList(){
- mapView.visibility = View.GONE
+ mapView?.visibility = View.GONE
stopsRecyclerView.visibility = View.VISIBLE
locationIcon?.visibility = View.GONE
busPositionsIconButton?.visibility = View.GONE
@@ -447,7 +447,7 @@
private fun hideStopListAndShowMap(){
stopsRecyclerView.visibility = View.GONE
- mapView.visibility = View.VISIBLE
+ mapView?.visibility = View.VISIBLE
locationIcon?.visibility = View.VISIBLE
busPositionsIconButton.visibility = View.VISIBLE
@@ -648,7 +648,7 @@
//Log.d(DEBUG_TAG, "Received ${updates.size} updates for the positions")
val updates = pair.first
val vehiclesNotOnCorrectDir = pair.second
- if(mapView.visibility == View.GONE || patternShown ==null){
+ if(mapView?.visibility == View.GONE || patternShown ==null){
//DO NOTHING
Log.w(DEBUG_TAG, "not doing anything because map is not visible")
return@observe
@@ -1063,7 +1063,6 @@
override fun onPause() {
super.onPause()
- mapView.onPause()
if(usingMQTTPositions) livePositionsViewModel.stopMatoUpdates()
pausedFragment = true
//save map
@@ -1076,7 +1075,6 @@
override fun onStop() {
super.onStop()
- mapView.onStop()
if(locationInitialized)
shouldMapLocationBeReactivated = locationComponent.isLocationComponentEnabled
else
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
@@ -21,8 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.location.Criteria;
-import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -61,13 +59,11 @@
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.data.PreferencesHolder;
-import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher;
import it.reyboz.bustorino.middleware.AsyncStopsSearcher;
import it.reyboz.bustorino.middleware.BarcodeScanContract;
import it.reyboz.bustorino.middleware.BarcodeScanOptions;
import it.reyboz.bustorino.middleware.BarcodeScanUtils;
-import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
@@ -112,7 +108,7 @@
private int searchMode;
//private ImageButton addToFavorites;
//// HIDDEN BUT IMPORTANT ELEMENTS ////
- FragmentManager childFragMan;
+ private FragmentManager childFragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
@@ -171,7 +167,7 @@
boolean pendingIntroRun = false;
boolean pendingNearbyStopsFragmentRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
- AppLocationManager locationManager;
+ //AppLocationManager locationManager;
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<>() {
@Override
@@ -187,11 +183,13 @@
|| Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) {
locationPermissionGranted = true;
Log.w(DEBUG_TAG, "Starting position");
- if (mListener != null && getContext() != null) {
+ /*if (mListener != null && getContext() != null) {
if (locationManager == null)
locationManager = AppLocationManager.getInstance(getContext());
locationManager.addLocationRequestFor(requester);
}
+
+ */
// show nearby fragment
//showNearbyStopsFragment();
Log.d(DEBUG_TAG, "We have location permission");
@@ -205,9 +203,9 @@
});
- private final LocationCriteria cr = new LocationCriteria(2000, 10000);
+ //private final LocationCriteria cr = new LocationCriteria(2000, 10000);
//Location
- private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
+ /*private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
@Override
public void onLocationChanged(Location loc) {
@@ -255,7 +253,7 @@
}
};
-
+ */
//// ACTIVITY ATTACHED (LISTENER ///
private CommonFragmentListener mListener;
@@ -337,15 +335,14 @@
fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame);
setSearchModeBusStopID();
-
-
+ /*
cr.setAccuracy(Criteria.ACCURACY_FINE);
cr.setAltitudeRequired(false);
cr.setBearingRequired(false);
cr.setCostAllowed(true);
cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
-
- locationManager = AppLocationManager.getInstance(requireContext());
+ */
+ //locationManager = AppLocationManager.getInstance(requireContext());
Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null));
@@ -470,8 +467,8 @@
final Context con = requireContext();
Log.w(DEBUG_TAG, "OnResume called, setupOnStart: "+ setupOnStart);
- if (locationManager == null)
- locationManager = AppLocationManager.getInstance(con);
+ //if (locationManager == null)
+ // locationManager = AppLocationManager.getInstance(con);
//recheck the introduction activity has been run
if(pendingIntroRun && PreferencesHolder.hasIntroFinishedOneShot(con)){
//request position permission if needed
@@ -487,8 +484,8 @@
}
if(Permissions.bothLocationPermissionsGranted(con)){
Log.d(DEBUG_TAG, "Location permission OK");
- if(!locationManager.isRequesterRegistered(requester))
- locationManager.addLocationRequestFor(requester);
+ //if(!locationManager.isRequesterRegistered(requester))
+ // locationManager.addLocationRequestFor(requester);
} //don't request permission
// if we have a pending stopID request, do it
Log.d(DEBUG_TAG, "Pending stop ID for arrivals: "+pendingStopID);
@@ -533,7 +530,7 @@
@Override
public void onPause() {
//mainHandler = null;
- locationManager.removeLocationRequestFor(requester);
+ //locationManager.removeLocationRequestFor(requester);
super.onPause();
fragmentHelper.setBlockAllActivities(true);
fragmentHelper.stopLastRequestIfNeeded(true);
@@ -648,7 +645,6 @@
}
@Nullable
- @org.jetbrains.annotations.Nullable
@Override
public View getBaseViewForSnackBar() {
return coordLayout;
@@ -718,7 +714,7 @@
hideKeyboard();
if (pendingNearbyStopsFragmentRequest) {
- locationManager.removeLocationRequestFor(requester);
+ //locationManager.removeLocationRequestFor(requester);
pendingNearbyStopsFragmentRequest = false;
}
}
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
@@ -133,8 +133,8 @@
// Init the MapView
mapView = rootView.findViewById(R.id.libreMapView)
- mapView.onCreate(savedInstanceState)
- mapView.getMapAsync(this)
+ mapView!!.onCreate(savedInstanceState)
+ mapView!!.getMapAsync(this)
//init bottom sheet
val bottomSheet = rootView.findViewById<RelativeLayout>(R.id.bottom_sheet)
@@ -166,7 +166,7 @@
val location = locationComponent.lastKnownLocation
location?.let {
- mapView.getMapAsync { map ->
+ mapView?.getMapAsync { map ->
map.animateCamera(CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder().target(LatLng(location.latitude, location.longitude)).build()), 500)
}
@@ -260,8 +260,8 @@
addImagesStyle(style)
//init stop layer with this
- val stopsInCache = stopsViewModel.getAllStopsLoaded()
- if(stopsInCache.isEmpty())
+ val stopsInCache = stopsViewModel.stopsToShow.value
+ if(stopsInCache.isNullOrEmpty())
initStopsLayer(style, null)
else
displayStops(stopsInCache)
@@ -465,13 +465,22 @@
override fun onPause() {
super.onPause()
- mapView.onPause()
Log.d(DEBUG_TAG, "Fragment paused")
map?.let{
//if map is initialized
mapStateViewModel.saveMapState(it)
}
+ try{
+ //save last location
+ map?.locationComponent?.let{
+ if(locationInitialized && it.isLocationComponentActivated){
+ stopsViewModel.lastUserLocation = it.lastKnownLocation
+ }
+ }
+ }catch (e: Exception){
+ Log.w(DEBUG_TAG, "Cannot save lastKnowLocation from map location component,error: ${e.message}")
+ }
mapStateViewModel.lastOpenStopID.postValue(shownStopInBottomSheet?.ID)
if (livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.stopMatoUpdates()
@@ -479,20 +488,7 @@
override fun onStop() {
super.onStop()
- mapView.onStop()
Log.d(DEBUG_TAG, "Fragment stopped!")
- /* stopsViewModel.savedState = Bundle().let {
- mapView.onSaveInstanceState(it)
- it
- }
- */
- //save last location
- if (locationInitialized)
- map?.locationComponent?.lastKnownLocation?.let{
- stopsViewModel.lastUserLocation = it
- }
-
-
}
override fun onMapDestroy() {
@@ -614,8 +610,13 @@
override fun onSuccess(res: LocationEngineResult?) {
Log.d(DEBUG_TAG, "Got the last location, ${res?.lastLocation}")
res?.lastLocation?.let { loc ->
- if(mapInitialized)
- map?.cameraPosition = CameraPosition.Builder().target(LatLng(loc.latitude, loc.longitude)).build()
+ if(mapInitialized){
+ val newLocation = LatLng(loc.latitude, loc.longitude)
+ //center the position only if it is close enough
+ if(newLocation.distanceTo(DEFAULT_LATLNG) < MAX_DIST_KM * 1000)
+ map?.cameraPosition = CameraPosition.Builder().target(LatLng(loc.latitude, loc.longitude)).build()
+
+ }
else
mapStateViewModel.locationToShow = loc
}
@@ -630,6 +631,10 @@
})
}
+ if(locationEnabledOnDevice){
+ setFollowUserLocation(true)
+ }
+
}
override fun onMapLocationEnabled(active: Boolean) {
@@ -644,11 +649,7 @@
if(locationInitialized && !receivedFirstLocation) {
//only zoom if the user position is close enough to the center
val newPoint = LatLng(it.latitude, it.longitude)
- if(newPoint.distanceTo(LatLng(
- MapLibreFragment.DEFAULT_CENTER_LAT,
- MapLibreFragment.DEFAULT_CENTER_LON
- ))
- > MAX_DIST_KM * 1000){
+ if(newPoint.distanceTo(DEFAULT_LATLNG) > MAX_DIST_KM * 1000){
//show Toast
if(!shownToastNoPosition) context?.let{ c->
Toast.makeText(c, R.string.too_far_not_showing_location, Toast.LENGTH_LONG).show()
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -115,6 +115,7 @@
private ArrayList<Stop> currentNearbyStops = new ArrayList<>();
private LocationShowingStatus showingStatus = LocationShowingStatus.NO_PERMISSION;
+ private boolean isLocationEnabled = false;
private final FusedNativeLocationProvider.LocationUpdateListener locationUpdateListener = new FusedNativeLocationProvider.LocationUpdateListener() {
@Override
@@ -125,6 +126,7 @@
@Override
public void onFusedStatusChanged(boolean isEnabled) {
Log.d(DEBUG_TAG, "Location provider is enabled: " + isEnabled);
+ isLocationEnabled = isEnabled;
if(isEnabled){
setShowingStatus(LocationShowingStatus.SEARCHING);
} else{
@@ -363,6 +365,11 @@
if(newStatus == showingStatus){
return;
}
+ if(!isLocationEnabled && newStatus!=LocationShowingStatus.NO_PERMISSION){
+ Log.d(DEBUG_TAG, "asked to show status: "+newStatus+" but the position is disabled");
+ newStatus = LocationShowingStatus.DISABLED;
+ }
+
switch (newStatus){
case FIRST_FIX:
circlingProgressBar.setVisibility(View.GONE);
@@ -377,10 +384,10 @@
messageTextView.setVisibility(View.VISIBLE);
break;
case DISABLED:
- if (showingStatus== LocationShowingStatus.SEARCHING){
+ //if (showingStatus== LocationShowingStatus.SEARCHING){
circlingProgressBar.setVisibility(View.GONE);
loadingTextView.setVisibility(View.GONE);
- }
+ //}
messageTextView.setText(R.string.enable_location_message);
messageTextView.setVisibility(View.VISIBLE);
break;
@@ -415,6 +422,8 @@
gridRecyclerView.setAdapter(null);
Log.d(DEBUG_TAG,"On paused called");
+
+ locationProvider.stopUpdates();
}
@Override
@@ -465,6 +474,10 @@
if(BuildConfig.DEBUG)
Log.d(DEBUG_TAG, "Max distance for stops: "+MAX_DISTANCE+ ", Min number of stops: "+MIN_NUM_STOPS);
+ if(!locationProvider.isRunning()){
+ startLocationUpdatesByType();
+ }
+
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ResultListFragment.java
@@ -103,22 +103,6 @@
}
}
- /**
- * Check if the last Bus Stop is in the favorites
- * @return true if it iss
- */
- public boolean isStopInFavorites(String busStopId) {
- boolean found = false;
-
- // no stop no party
- if(busStopId != null) {
- SQLiteDatabase userDB = UserDB.getInstance(getContext()).getReadableDatabase();
- found = UserDB.isStopInFavorites(userDB, busStopId);
- }
-
- return found;
- }
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
@@ -15,13 +15,12 @@
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import it.reyboz.bustorino.BuildConfig;
-import it.reyboz.bustorino.R;
import java.util.Map;
@@ -33,6 +32,7 @@
protected void setOption(String optionName, boolean value) {
Context mContext = getContext();
+ assert mContext != null;
SharedPreferences.Editor editor = mContext.getSharedPreferences(PREF_FILE, MODE_PRIVATE).edit();
editor.putBoolean(optionName, value);
editor.commit();
@@ -108,7 +108,49 @@
}
});
}
+ /*protected void runActionFavorites(@NonNull Stop s, @NonNull FavoritesChangeWorker.Action action, @NonNull FavoritesChangeWorker.Companion.ResultListener resultListener){
+ Context mContext = requireContext();
+
+ WorkManager workManager = WorkManager.getInstance(mContext);
+
+ WorkRequest req = FavoritesChangeWorker.makeRequest(s, action);
+ workManager.enqueue(req);
+ Context appContext = mContext.getApplicationContext();
+
+ //FavoritesChangeWorker.registerListener(mContext, getViewLifecycleOwner(), s, action, resultListener);
+ workManager.getWorkInfosByTagLiveData(FavoritesChangeWorker.getTag(s, action))
+ .observe(getViewLifecycleOwner(), wi -> {
+ Log.d("BusTO-BaseFragment", "workinfo for stop "+s.ID+" has arrived");
+ if(wi.isEmpty()){
+ return;
+ }
+ WorkInfo workInfo = wi.get(wi.size() - 1);
+ Data progress = wi.get(wi.size()-1).getProgress();
+
+ int actvalue = progress.getInt(ACTION_ARG,-1);
+ boolean done = progress.getBoolean(DONE_ARG, false);
+ if (done) {
+ // at this point the action should be just ADD or REMOVE
+
+ if (actvalue == FavoritesChangeWorker.Action.ADD.getValue()) {
+ // now added
+ Toast.makeText(appContext, R.string.added_in_favorites, Toast.LENGTH_SHORT).show();
+ } else if (actvalue == FavoritesChangeWorker.Action.REMOVE.getValue()) {
+ // now removed
+ Toast.makeText(appContext, R.string.removed_from_favorites, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // wtf
+ Toast.makeText(appContext, R.string.cant_add_to_favorites, Toast.LENGTH_SHORT).show();
+ }
+ Log.d("busTO-ScreenBaseFragm", "favorites action="+actvalue+ ",done="+done);
+
+ // aggiorna UI
+ resultListener.doStuffWithResult(done);
+ });
+ }
+ */
public interface LocationRequestListener{
void onPermissionResult(boolean locationGranted);
diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/AppLocationManager.kt b/app/src/main/java/it/reyboz/bustorino/middleware/AppLocationManager.kt
--- a/app/src/main/java/it/reyboz/bustorino/middleware/AppLocationManager.kt
+++ b/app/src/main/java/it/reyboz/bustorino/middleware/AppLocationManager.kt
@@ -24,9 +24,7 @@
import android.os.Bundle
import android.util.Log
import androidx.core.content.ContextCompat
-import androidx.core.location.LocationListenerCompat
import it.reyboz.bustorino.util.LocationCriteria
-import it.reyboz.bustorino.util.Permissions
import java.lang.ref.WeakReference
import kotlin.math.min
diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java b/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
--- a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
+++ b/app/src/main/java/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
@@ -22,6 +22,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
@@ -257,84 +258,7 @@
@Override
public void run() {
- final NextGenDB nextGenDB = NextGenDB.getInstance(context);
- //ContentValues[] values = new ContentValues[routesToInsert.size()];
- ArrayList<ContentValues> branchesValues = new ArrayList<>(routesToInsert.size()*4);
- ArrayList<ContentValues> connectionsVals = new ArrayList<>(routesToInsert.size()*4);
- long starttime,endtime;
- for (Route r:routesToInsert){
- //if it has received an interrupt, stop
- if(Thread.interrupted()) return;
- //otherwise, build contentValues
- final ContentValues cv = new ContentValues();
- cv.put(BranchesTable.COL_BRANCHID,r.branchid);
- cv.put(LinesTable.COLUMN_NAME,r.getName());
- cv.put(BranchesTable.COL_DIRECTION,r.destinazione);
- cv.put(BranchesTable.COL_DESCRIPTION,r.description);
- for (int day :r.serviceDays) {
- switch (day){
- case Calendar.MONDAY:
- cv.put(BranchesTable.COL_LUN,1);
- break;
- case Calendar.TUESDAY:
- cv.put(BranchesTable.COL_MAR,1);
- break;
- case Calendar.WEDNESDAY:
- cv.put(BranchesTable.COL_MER,1);
- break;
- case Calendar.THURSDAY:
- cv.put(BranchesTable.COL_GIO,1);
- break;
- case Calendar.FRIDAY:
- cv.put(BranchesTable.COL_VEN,1);
- break;
- case Calendar.SATURDAY:
- cv.put(BranchesTable.COL_SAB,1);
- break;
- case Calendar.SUNDAY:
- cv.put(BranchesTable.COL_DOM,1);
- break;
- }
- }
- if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
- cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
-
- //values[routesToInsert.indexOf(r)] = cv;
- branchesValues.add(cv);
- if(r.getStopsList() != null)
- for(int i=0; i<r.getStopsList().size();i++){
- String stop = r.getStopsList().get(i);
- final ContentValues connVal = new ContentValues();
- connVal.put(ConnectionsTable.COLUMN_STOP_ID,stop);
- connVal.put(ConnectionsTable.COLUMN_ORDER,i);
- connVal.put(ConnectionsTable.COLUMN_BRANCH,r.branchid);
-
- //add to global connVals
- connectionsVals.add(connVal);
- }
- }
- starttime = System.currentTimeMillis();
- ContentResolver cr = context.getContentResolver();
- try {
- cr.bulkInsert(Uri.parse("content://" + AppDataProvider.AUTHORITY + "/branches/"), branchesValues.toArray(new ContentValues[0]));
- endtime = System.currentTimeMillis();
- Log.d("DataDownload", "Inserted branches, took " + (endtime - starttime) + " ms");
- } catch (SQLException exc){
- Log.e("AsyncDataDownload","Inserting data: some error happened, aborting the database insert");
- exc.printStackTrace();
- return;
- }
-
- if (connectionsVals.size()>0) {
- starttime = System.currentTimeMillis();
- ContentValues[] valArr = connectionsVals.toArray(new ContentValues[0]);
- Log.d("DataDownloadInsert", "inserting " + valArr.length + " connections");
- 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();
+ NextGenDB.insertBranchesIntoDB(context, routesToInsert);
}
}
}
diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java b/app/src/main/java/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java
deleted file mode 100644
--- a/app/src/main/java/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- BusTO - Middleware components
- Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
- */
-
-package it.reyboz.bustorino.middleware;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-import android.widget.Toast;
-import it.reyboz.bustorino.R;
-import it.reyboz.bustorino.backend.Stop;
-import it.reyboz.bustorino.data.AppDataProvider;
-import it.reyboz.bustorino.data.UserDB;
-
-/**
- * Handler to add or remove or toggle a Stop in your favorites
- */
-public class AsyncStopFavoriteAction extends AsyncTask<Stop, Void, Boolean> {
- private final Context context;
- private final Uri FAVORITES_URI = AppDataProvider.getUriBuilderToComplete().appendPath(
- AppDataProvider.FAVORITES).build();
-
- /**
- * Kind of actions available
- */
- public enum Action { ADD, REMOVE, TOGGLE , UPDATE};
-
- /**
- * Action chosen
- *
- * Note that TOGGLE is not converted to ADD or REMOVE.
- */
- private Action action;
-
- // extra stuff to do after we've done it
- private ResultListener listener;
- /**
- * Constructor
- *
- * @param context
- * @param action
- */
- public AsyncStopFavoriteAction(Context context, Action action, ResultListener listener) {
- this.context = context.getApplicationContext();
- this.action = action;
- this.listener = listener;
- }
-
- @Override
- protected Boolean doInBackground(Stop... stops) {
- boolean result = false;
-
- Stop stop = stops[0];
-
- // check if the request has sense
- if(stop != null) {
-
- // get a writable database
- UserDB userDatabase = UserDB.getInstance(context);
- SQLiteDatabase db = userDatabase.getWritableDatabase();
-
- // eventually toggle the status
- if(Action.TOGGLE.equals(action)) {
- if(UserDB.isStopInFavorites(db, stop.ID)) {
- action = Action.REMOVE;
- } else {
- action = Action.ADD;
- }
- }
-
- // at this point the action is just ADD or REMOVE
-
- // add or remove?
- if(Action.ADD.equals(action)) {
- // add
- result = UserDB.addOrUpdateStop(stop, db);
- } else if (Action.UPDATE.equals(action)){
-
- result = UserDB.updateStop(stop, db);
- } else {
- // remove
- result = UserDB.deleteStop(stop, db);
- }
-
- // These should NOT be closed: the database is a singleton, the connections are recycled.
- //db.close();
- }
-
- return result;
- }
-
- /**
- * Callback fired when everything was done
- *
- * @param result
- */
- @Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
-
- if(result) {
- UserDB.notifyContentProvider(context);
- // at this point the action should be just ADD or REMOVE
- if(Action.ADD.equals(action)) {
- // now added
- Toast.makeText(this.context, R.string.added_in_favorites, Toast.LENGTH_SHORT).show();
- } else if (Action.REMOVE.equals(action)) {
- // now removed
- Toast.makeText(this.context, R.string.removed_from_favorites, Toast.LENGTH_SHORT).show();
- }
- } else {
- // wtf
- Toast.makeText(this.context, R.string.cant_add_to_favorites, Toast.LENGTH_SHORT).show();
- }
- listener.doStuffWithResult(result);
- Log.d("BusTO FavoritesAction", "Action "+action+" completed");
- }
-
- public interface ResultListener{
- /**
- * Do what you need to to update the UI with the result
- * @param result true if the action is done
- */
- void doStuffWithResult(Boolean result);
- }
-
-}
diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/CoroutineFavoriteAction.kt b/app/src/main/java/it/reyboz/bustorino/middleware/CoroutineFavoriteAction.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/middleware/CoroutineFavoriteAction.kt
@@ -0,0 +1,67 @@
+package it.reyboz.bustorino.middleware
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.widget.Toast
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.UserDB
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class CoroutineFavoriteAction(
+ context: Context,
+ private var action: Action,
+ private val listener: ResultListener
+) {
+ private val context = context.applicationContext
+
+ enum class Action { ADD, REMOVE, TOGGLE, UPDATE }
+
+ fun interface ResultListener {
+ fun doStuffWithResult(result: Boolean)
+ }
+
+ fun execute(stop: Stop) {
+ CoroutineScope(Dispatchers.IO).launch {
+ val result = doInBackground(stop)
+ withContext(Dispatchers.Main) {
+ onPostExecute(result)
+ }
+ }
+ }
+
+ private fun doInBackground(stop: Stop): Boolean {
+ val userDB = UserDB.getInstance(context)
+
+ if (action == Action.TOGGLE) {
+ action = if (userDB.isStopInFavorites(stop.ID)) Action.REMOVE else Action.ADD
+ }
+
+ return when (action) {
+ Action.ADD -> userDB.addOrUpdateStop(stop)
+ Action.UPDATE -> userDB.updateStop(stop)
+ Action.REMOVE -> userDB.deleteStop(stop)
+ Action.TOGGLE -> false // irraggiungibile, ma richiesto da when exhaustive
+ }
+ }
+
+ private fun onPostExecute(result: Boolean) {
+ if (result) {
+ UserDB.notifyContentProvider(context)
+ when (action) {
+ Action.ADD -> Toast.makeText(context, R.string.added_in_favorites, Toast.LENGTH_SHORT).show()
+ Action.REMOVE -> Toast.makeText(context, R.string.removed_from_favorites, Toast.LENGTH_SHORT).show()
+ else -> Unit
+ }
+ } else {
+ Toast.makeText(context, R.string.cant_add_to_favorites, Toast.LENGTH_SHORT).show()
+ }
+ listener.doStuffWithResult(result)
+ Log.d("BusTO FavoritesAction", "Action $action completed")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
@@ -23,6 +23,8 @@
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.map
+import androidx.lifecycle.switchMap
import androidx.lifecycle.viewModelScope
import it.reyboz.bustorino.backend.*
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher
@@ -37,25 +39,33 @@
class ArrivalsViewModel(application: Application): AndroidViewModel(application) {
// Arrivals of palina
- val appContext: Context
+ val appContext: Context = application.applicationContext
+ private val executor = Executors.newFixedThreadPool(2)
+ private val oldRepo = OldDataRepository(executor, application)
+
- val palinaLiveData = MediatorLiveData<Palina>()
+ val palinaFromArrivals = MediatorLiveData<Palina>()
+ val palinaToShow = MediatorLiveData<Palina>()
val sourcesLiveData = MediatorLiveData<Passaggio.Source>()
val resultLiveData = MutableLiveData<Fetcher.Result>()
val currentFetchers = MediatorLiveData<List<ArrivalsFetcher>>()
- /// OLD REPO for stops instance
- private val executor = Executors.newFixedThreadPool(2)
- private val oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+ private val stopID = MutableLiveData<String>()
+
+ fun setStopId(stopId: String) { stopID.value = (stopId) }
+ val stopFavoritesData = stopID.switchMap { oldRepo.getFavoritesLiveDataByStopId(listOf(it))}
+ val stopInFavorites = stopFavoritesData.map { it!=null && it.isNotEmpty() }
+
+ /// OLD REPO for stops instance
private var stopIdRequested = ""
private val stopFromDB = MutableLiveData<Stop>()
val arrivalsRequestRunningLiveData = MutableLiveData(false)
- private val oldRepoStopCallback = OldDataRepository.Callback<List<Stop>>{ stopListRes ->
+ private val oldRepoStopCallback = OldDataRepository.Callback<ArrayList<Stop>>{ stopListRes ->
if(stopIdRequested.isEmpty()) return@Callback
if(stopListRes.isSuccess) {
@@ -72,15 +82,32 @@
}
init {
- appContext = application.applicationContext
- palinaLiveData.addSource(stopFromDB){
+ palinaFromArrivals.addSource(stopFromDB){
s ->
- val hasSource = palinaLiveData.value?.passaggiSourceIfAny
- Log.d(DEBUG_TAG, "Have current palina ${palinaLiveData.value!=null}, source passaggi $hasSource, new incoming stop $s from database")
- val newp = if(palinaLiveData.value == null) Palina(s) else Palina.mergePaline(palinaLiveData.value, Palina(s))
- Log.d(DEBUG_TAG, "Merged palina: $newp, num passages: ${newp?.totalNumberOfPassages}, has coords: ${newp?.hasCoords()}")
- newp?.let { pal -> palinaLiveData.postValue(pal) }
+ val hasSource = palinaFromArrivals.value?.passaggiSourceIfAny
+ //Log.d(DEBUG_TAG, "Have current palina ${palinaLiveData.value!=null}, source passaggi $hasSource, new incoming stop $s from database")
+ val newp = if(palinaFromArrivals.value == null) Palina(s) else Palina.mergePaline(palinaFromArrivals.value, Palina(s))
+ //Log.d(DEBUG_TAG, "Merged palina: $newp, num passages: ${newp?.totalNumberOfPassages}, has coords: ${newp?.hasCoords()}")
+ newp?.let { pal -> palinaFromArrivals.postValue(pal) }
+ }
+ palinaToShow.addSource(stopFavoritesData){ dat ->
+ val current = palinaFromArrivals.value
+ Log.d(DEBUG_TAG, "have palina $current and favorites data: $dat")
+ if(dat!=null && current!=null){
+ if(dat.size>0 && dat[0].stopUserName!=null) // is in the favorites
+ current.stopUserName = dat[0].stopUserName
+ //set new data in palinaLiveData
+ palinaToShow.value = current
+ }
+ }
+ palinaToShow.addSource(palinaFromArrivals){ p->
+ stopFavoritesData.value?.let {it ->
+ if(it.isNotEmpty() && it[0].stopUserName!=null) {
+ p.stopUserName = it[0].stopUserName
+ }
+ }
+ palinaToShow.value = p
}
}
@@ -90,6 +117,7 @@
return palina
}
+
fun requestArrivalsForStop(stopId: String, fetchers: List<ArrivalsFetcher>){
val context = appContext //application.applicationContext
currentFetchers.value = fetchers
@@ -222,8 +250,8 @@
arrivalsRequestRunningLiveData.postValue(false)
resultLiveData.postValue(fetcherResult)
Log.d(DEBUG_TAG, "Have new result palina for stop ${palina.ID}, source ${palina.passaggiSourceIfAny} has coords: ${palina.hasCoords()}")
- Log.d(DEBUG_TAG, "Old palina liveData is: ${palinaLiveData.value?.stopDisplayName}, has Coords ${palinaLiveData.value?.hasCoords()}")
- palinaLiveData.postValue(Palina.mergePaline(palina, palinaLiveData.value))
+ Log.d(DEBUG_TAG, "Old palina liveData is: ${palinaFromArrivals.value?.stopDisplayName}, has Coords ${palinaFromArrivals.value?.hasCoords()}")
+ palinaFromArrivals.postValue(Palina.mergePaline(palina, palinaFromArrivals.value))
}
companion object{
const val DEBUG_TAG="BusTO-ArrivalsViMo"
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt
@@ -0,0 +1,92 @@
+package it.reyboz.bustorino.viewmodels
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.application
+import androidx.lifecycle.map
+import androidx.lifecycle.switchMap
+import androidx.lifecycle.viewModelScope
+import androidx.work.WorkInfo
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.StopFavoritesData
+import it.reyboz.bustorino.data.DBUpdateWorker.Companion.getWorkInfoLiveData
+import it.reyboz.bustorino.data.FavoritesLiveData
+import it.reyboz.bustorino.data.OldDataRepository
+import it.reyboz.bustorino.data.QueryLiveData
+import kotlinx.coroutines.launch
+import java.util.concurrent.Executors
+
+class FavoritesViewModel(application: Application) : AndroidViewModel(application) {
+
+ val oldRepo: OldDataRepository
+
+ init {
+ val executor = Executors.newCachedThreadPool()
+ oldRepo = OldDataRepository(executor, application)
+ }
+ /*var favoritesLiveData: FavoritesLiveData? = null
+
+ override fun onCleared() {
+ if (favoritesLiveData != null) favoritesLiveData!!.onClear()
+ super.onCleared()
+ }
+
+ val favorites: FavoritesLiveData
+ get() {
+ if (favoritesLiveData == null) {
+ favoritesLiveData = FavoritesLiveData(application, true)
+ }
+ return favoritesLiveData!!
+ }
+*/
+ val isDBUpdating = getWorkInfoLiveData(application).map { wilist ->
+ var isUpdating = false
+ if(wilist.isNotEmpty()){
+ val wi = wilist[0]
+ isUpdating = wi.state == WorkInfo.State.RUNNING
+ }
+ isUpdating
+ }
+
+
+ // ---- NEW CODE -----
+ // this code is not active now, but it is gonna be useful for the day when the ContentObserver is gonna be dismissed
+ //for all favorites
+ val favoritesWithStop = MediatorLiveData<List<Stop>>()
+ val favoritesNoStop = oldRepo.getFavoritesLiveData()
+
+ val stopsForFavorites = favoritesNoStop.switchMap {
+ val sids = it.map { d-> d.stopID }
+ oldRepo.getStopsForIdsLiveData(sids)
+ }
+
+ init{
+ // this fetches the stops when I have gotten the favorites
+ favoritesWithStop.addSource(favoritesNoStop){ dat ->
+ if(dat!=null) stopsForFavorites.value?.let{ stops ->
+ matchFavoritesStopsAndUpdate(dat, stops)
+ }
+ }
+
+ favoritesWithStop.addSource(stopsForFavorites) { stops ->
+ favoritesNoStop.value?.let { fav ->
+ if(stops!=null){
+ matchFavoritesStopsAndUpdate(fav, stops)
+ }
+ }
+ }
+ }
+ fun matchFavoritesStopsAndUpdate(fav: List<StopFavoritesData>, stops: List<Stop>) {
+ //copy favorites info
+ for(s in stops) {
+ val di = fav.first{ it.stopID == s.ID}
+ di.addToStop(s)
+ }
+ favoritesWithStop.value = stops
+ }
+ companion object {
+ const val DEBUG_TAG = "BusTO-FavoritesViewM"
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
@@ -40,7 +40,7 @@
init {
gtfsRepo = GtfsRepository(application)
- oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+ oldRepo = OldDataRepository(executor, application)
}
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
@@ -22,25 +22,27 @@
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.switchMap
import it.reyboz.bustorino.backend.Stop
-import it.reyboz.bustorino.data.NextGenDB
import it.reyboz.bustorino.data.OldDataRepository
import org.maplibre.android.geometry.LatLngBounds
+import org.maplibre.geojson.BoundingBox
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
+import kotlin.math.abs
class StopsMapViewModel(application: Application): AndroidViewModel(application) {
private val executor = Executors.newFixedThreadPool(2)
- private val oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
-
- val stopsToShow = MutableLiveData(ArrayList<Stop>())
- private var stopsShownIDs = HashSet<String>()
- private var allStopsLoaded = HashMap<String,Stop>()
+ private val oldRepo = OldDataRepository(executor, application)
+ //private var stopsShownIDs = HashSet<String>()
+ //private var allStopsLoaded = HashMap<String,Stop>()
+ private val boundingBoxLoaded = MutableLiveData<BoundingBox>()
val stopsInBoundingBox = MutableLiveData<ArrayList<Stop>>()
private val callback =
@@ -51,11 +53,62 @@
}
}
- private val addStopsCallback =
+ fun getStopByID(id: String): Stop? {
+ return stopsToShow.value?.firstOrNull{ s-> s.ID == id}
+ }
+
+ fun getAllStopsLoaded(): ArrayList<Stop>?{
+ return stopsToShow.value
+ }
+
+ /*fun requestStopsInBoundingBox(bb: BoundingBox) {
+ bb.let {
+ Log.d(DEBUG_TAG, "Launching stop request")
+ oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback)
+ }
+ }
+
+ */
+ private fun updateBoundingBox(boundingBox: BoundingBox){
+ val current = boundingBoxLoaded.value
+ if(current == null){
+ boundingBoxLoaded.value = boundingBox
+ } else{
+ val bb = boundingBox
+ val mix = BoundingBox.fromLngLats(Math.min(current.west(), bb.west()),
+ Math.min(current.south(), bb.south()), Math.max(current.north(), bb.east()),
+ Math.max(current.north(), bb.north()))
+
+ boundingBoxLoaded.value = mix
+ }
+ }
+
+ fun loadStopsInLatLngBounds(bb: LatLngBounds){
+ val extra = 0.05
+ val deltaLong = abs(bb.longitudeEast - bb.longitudeWest) * extra
+ val deltaLat = abs(bb.latitudeNorth - bb.latitudeSouth) * extra
+
+ val newBB = BoundingBox.fromLngLats(bb.longitudeWest - deltaLat,
+ bb.latitudeSouth -deltaLong,
+ bb.longitudeEast + deltaLong,
+ bb.latitudeNorth +deltaLat)
+
+ updateBoundingBox(newBB)
+ /*Log.d(DEBUG_TAG, "Launching stop request")
+
+ oldRepo.requestStopsInArea(bb.latitudeSouth-deltaLat, bb.latitudeNorth+deltaLat,
+ bb.longitudeWest-deltaLong, bb.longitudeEast+deltaLong,
+ addStopsCallback)
+
+ */
+
+ }
+
+ /*private val addStopsCallback =
OldDataRepository.Callback<ArrayList<Stop>> { res ->
if(res.isSuccess) res.result?.let{ newStops ->
- val stopsAdd = stopsToShow.value ?: ArrayList()
- for (s in newStops){
+ //val stopsAdd = stopsToShow.value ?: ArrayList()
+ /*for (s in newStops){
if (s.ID !in stopsShownIDs){
stopsShownIDs.add(s.ID)
stopsAdd.add(s)
@@ -63,41 +116,30 @@
}
}
- stopsToShow.postValue(stopsAdd)
+ */
+ allStopsLoaded.clear()
+ for(stop in newStops){
+ allStopsLoaded[stop.ID] = stop
+ }
+ Log.d(DEBUG_TAG, "Loaded ${newStops.size} stops")
+ stopsToShow.postValue(newStops)
//Log.d(DEBUG_TAG, "Loaded ${stopsAdd.size} stops in total")
}
}
- fun getStopByID(id: String): Stop? {
- if (id in allStopsLoaded) return allStopsLoaded[id]
- else return null
+ */
+ /*stopsToShow.addSource(boundingBoxLoaded){
+ oldRepo.requestStopsInArea(it.south(), it.north(),
+ it.west(), it.east(),
+ addStopsCallback)
}
- fun getAllStopsLoaded(): ArrayList<Stop>{
- return ArrayList(allStopsLoaded.values)
- }
+ */
- /*fun requestStopsInBoundingBox(bb: BoundingBox) {
- bb.let {
- Log.d(DEBUG_TAG, "Launching stop request")
- oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback)
- }
+ val stopsToShow = boundingBoxLoaded.switchMap {
+ oldRepo.requestStopsInAreaLiveData(it.south(), it.north(), it.west(), it.east())
}
- */
- fun requestStopsInLatLng(bb: LatLngBounds) {
- bb.let {
- Log.d(DEBUG_TAG, "Launching stop request")
- oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast, callback)
- }
- }
- fun loadStopsInLatLngBounds(bb: LatLngBounds?){
- bb?.let {
- Log.d(DEBUG_TAG, "Launching stop request")
- oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast,
- addStopsCallback)
- }
- }
//this is only saved at the end, is it really necessary?
var lastUserLocation: Location? = null
diff --git a/app/src/main/res/layout/fragment_main_screen.xml b/app/src/main/res/layout/fragment_main_screen.xml
--- a/app/src/main/res/layout/fragment_main_screen.xml
+++ b/app/src/main/res/layout/fragment_main_screen.xml
@@ -121,7 +121,9 @@
android:layout_width="match_parent"
android:id="@+id/resultFrame"
android:layout_height="fill_parent"
- android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/>
+ android:layout_alignParentLeft="true"
+ android:layout_marginTop="5dp"
+ android:layout_alignParentStart="true"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
diff --git a/app/src/main/res/layout/fragment_nearby_stops.xml b/app/src/main/res/layout/fragment_nearby_stops.xml
--- a/app/src/main/res/layout/fragment_nearby_stops.xml
+++ b/app/src/main/res/layout/fragment_nearby_stops.xml
@@ -13,6 +13,7 @@
android:layout_marginTop="15dp"
android:paddingTop="3dp"
android:gravity="center_horizontal"
+ android:fontFamily="@font/lato_bold"
android:textSize="23sp"
android:layout_toLeftOf="@id/switchButton" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp" android:layout_marginRight="10dp" android:layout_marginEnd="10dp"
@@ -45,11 +46,18 @@
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
- android:layout_height="match_parent" android:layout_below="@id/horizontalProgressBar"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/horizontalProgressBar"
android:id="@+id/stopGridRecyclerView"
android:clickable="true"
- android:focusable="true" android:visibility="gone"
- android:clipChildren="false" android:clipToPadding="true" android:horizontalSpacing="10dp"
+ android:focusable="true"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="5dp"
+ android:layout_marginEnd="5dp"
+ android:visibility="gone"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:horizontalSpacing="10dp"
android:layout_alignParentBottom="true"
android:verticalSpacing="10dp"/>
@@ -75,11 +83,15 @@
<TextView
android:text="@string/enable_location_message"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:id="@+id/messageTextView"
+ android:layout_height="wrap_content"
+ android:id="@+id/messageTextView"
+ android:fontFamily="@font/lato_regular"
android:visibility="gone"
android:textAppearance="@style/Base.ThemeOverlay.AppCompat.Light"
- android:layout_below="@+id/titleTextView" android:layout_centerHorizontal="true" android:textSize="17sp"
- android:layout_marginTop="20dp"
+ android:layout_below="@+id/titleTextView"
+ android:layout_centerHorizontal="true"
+ android:textSize="17sp"
+ android:layout_margin="20dp"
/>
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
@@ -391,4 +391,6 @@
<string name="italian">Italian</string>
<string name="english">English</string>
<string name="checking_alerts_update">Checking new alerts now</string>
+ <string name="back_again_to_close">Press back again to close the app</string>
+
</resources>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Jul 11, 20:44 (14 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1995407
Default Alt Text
D239.1783795476.diff (122 KB)
Attached To
Mode
D239: Fix many bugs in map, favorites and snackbar, revise and modernize DB
Attached
Detach File
Event Timeline