Page MenuHomeGitPull.it

D239.1783795476.diff
No OneTemporary

Authored By
Unknown
Size
122 KB
Referenced Files
None
Subscribers
None

D239.1783795476.diff

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

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)

Event Timeline