diff --git a/0001-Changed-repository.patch b/0001-Changed-repository.patch new file mode 100644 --- /dev/null +++ b/0001-Changed-repository.patch @@ -0,0 +1,48 @@ +From 981fb167ec1cd255f2ec41cf58e9002fa90acbb5 Mon Sep 17 00:00:00 2001 +From: Andrea +Date: Mon, 9 Mar 2020 15:01:21 +0100 +Subject: [PATCH] Changed repository + +--- + build.gradle | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/build.gradle b/build.gradle +index 874e06b..41dcd4f 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -1,9 +1,8 @@ + buildscript { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + +@@ -16,10 +15,9 @@ buildscript { + } + allprojects { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + } +@@ -66,7 +64,7 @@ android { + } + + repositories { +- mavenCentral() ++ jcenter() + mavenLocal() + } + +-- +2.25.1.windows.1 + diff --git a/Changed-repository.diff b/Changed-repository.diff new file mode 100644 --- /dev/null +++ b/Changed-repository.diff @@ -0,0 +1,36 @@ +diff --git a/build.gradle b/build.gradle +index 874e06b..41dcd4f 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -1,9 +1,8 @@ + buildscript { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + +@@ -16,10 +15,9 @@ buildscript { + } + allprojects { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + } +@@ -66,7 +64,7 @@ android { + } + + repositories { +- mavenCentral() ++ jcenter() + mavenLocal() + } + diff --git a/Changed-repository.patch b/Changed-repository.patch new file mode 100644 --- /dev/null +++ b/Changed-repository.patch @@ -0,0 +1,48 @@ +From 981fb167ec1cd255f2ec41cf58e9002fa90acbb5 Mon Sep 17 00:00:00 2001 +From: Andrea +Date: Mon, 9 Mar 2020 15:01:21 +0100 +Subject: Changed repository + +--- + build.gradle | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/build.gradle b/build.gradle +index 874e06b..41dcd4f 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -1,9 +1,8 @@ + buildscript { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + +@@ -16,10 +15,9 @@ buildscript { + } + allprojects { + repositories { +- mavenCentral() ++ jcenter() + maven { url 'https://maven.google.com' } + google() +- jcenter() + + } + } +@@ -66,7 +64,7 @@ android { + } + + repositories { +- mavenCentral() ++ jcenter() + mavenLocal() + } + +-- +2.25.1.windows.1 + diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -1,84 +1,85 @@ -buildscript { - repositories { - jcenter() - maven { url 'https://maven.google.com' } - google() - - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' - } - ext { - support_ver = "25.4.0" - } -} -allprojects { - repositories { - jcenter() - maven { url 'https://maven.google.com' } - google() - - } -} - -apply plugin: 'com.android.application' - -android { - compileSdkVersion 25 - buildToolsVersion '27.0.3' - - defaultConfig { - applicationId "it.reyboz.bustorino" - minSdkVersion 9 - targetSdkVersion 25 - versionCode 27 - versionName "1.11" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - resources.srcDirs = ['src'] - aidl.srcDirs = ['src'] - renderscript.srcDirs = ['src'] - res.srcDirs = ['res'] - assets.srcDirs = ['assets'] - } - } - buildTypes { - debug { - applicationIdSuffix ".debug" - versionNameSuffix "-dev" - } - } - - lintOptions { - abortOnError false - } - - repositories { - jcenter() - mavenLocal() - } - - dependencies { - implementation "com.android.support:support-v4:$support_ver" - implementation "com.android.support:appcompat-v7:$support_ver" - implementation "com.android.support:design:$support_ver" - implementation "com.android.support:recyclerview-v7:$support_ver" - implementation "com.android.support:preference-v7:$support_ver" - implementation "com.android.support:cardview-v7:$support_ver" - - - implementation 'org.jsoup:jsoup:1.11.3' - implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' - implementation 'com.android.volley:volley:1.1.1' - } -} +buildscript { + repositories { + jcenter() + maven { url 'https://maven.google.com' } + google() + + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.1.0' + } + ext { + support_ver = "25.4.0" + } +} +allprojects { + repositories { + jcenter() + maven { url 'https://maven.google.com' } + google() + + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "it.reyboz.bustorino" + minSdkVersion 9 + targetSdkVersion 25 + versionCode 27 + versionName "1.11" + vectorDrawables.useSupportLibrary = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + buildTypes { + debug { + applicationIdSuffix ".debug" + versionNameSuffix "-dev" + } + } + + lintOptions { + abortOnError false + } + + repositories { + jcenter() + mavenLocal() + } + + dependencies { + implementation "com.android.support:support-v4:$support_ver" + implementation "com.android.support:appcompat-v7:$support_ver" + implementation "com.android.support:design:$support_ver" + implementation "com.android.support:recyclerview-v7:$support_ver" + implementation "com.android.support:preference-v7:$support_ver" + implementation "com.android.support:cardview-v7:$support_ver" + + + implementation 'org.jsoup:jsoup:1.11.3' + implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' + implementation 'com.android.volley:volley:1.1.1' + } +} diff --git a/gradlew.bat b/gradlew.bat --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/res/drawable-xxxhdpi/qrcode_button_custom.xml b/res/drawable-xxxhdpi/qrcode_button_custom.xml --- a/res/drawable-xxxhdpi/qrcode_button_custom.xml +++ b/res/drawable-xxxhdpi/qrcode_button_custom.xml @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_star_filled.xml b/res/drawable/ic_star_filled.xml new file mode 100644 --- /dev/null +++ b/res/drawable/ic_star_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_star_outline.xml b/res/drawable/ic_star_outline.xml new file mode 100644 --- /dev/null +++ b/res/drawable/ic_star_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/layout/fragment_list_view.xml b/res/layout/fragment_list_view.xml --- a/res/layout/fragment_list_view.xml +++ b/res/layout/fragment_list_view.xml @@ -4,18 +4,36 @@ android:layout_width="match_parent" android:layout_height="match_parent" > + + android:id="@+id/messageTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginLeft="16dp" + android:layout_marginTop="10dp" + android:layout_marginEnd="307dp" + android:layout_marginRight="10dp" + android:layout_marginBottom="10dp" + android:layout_toStartOf="@+id/addToFavorites" + android:layout_toLeftOf="@+id/addToFavorites" + android:gravity="center_vertical" + android:minHeight="48dp" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + + - - - + + + + \ No newline at end of file diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1,111 +1,111 @@ - - - - - Stai utilizzando l\'ultimo ritrovato in materia di rispetto della tua privacy. - Cerca - QR Code - Numero fermata - Nome fermata - Inserisci il numero della fermata - Inserisci il nome della fermata - Verifica l\'accesso ad Internet! - Sembra che nessuna fermata abbia questo nome - Errore di lettura del sito 5T/GTT (dannato sito!) - Fermata: %1$s - Linee: %1$s - Scegli la fermata… - Nessun passaggio - Nessun QR code - Preferiti - Aiuto - Informazioni - Più informazioni - News - Invia bug - Codice sorgente - Licenza - Incontra l\'autore - Fermata aggiunta ai preferiti - Impossibile aggiungere ai preferiti (memoria piena o database corrotto?)! - Preferiti - Tocca il nome di una fermata per aggiungerla in questa lista - Rimuovi - Rinomina - Rinomina fermata - Reset - Informazioni - Tocca il nome della fermata per aggiungerla ai preferiti\n\nCome leggere gli orari:\n   12:56* Orario in tempo reale\n   12:56   Orario programmato\n\nTrascina giù per aggiornare l\'orario. - OK ! - -Benvenuto! - -

Grazie per aver scelto BusTO, un\'app indipendente da GTT/5T, per spostarsi a Torino attraverso software libero:

- -

Perché usare BusTO?

-

- - Non sei monitorato
- - Non ci sono pubblicità
- - La tua privacy è al sicuro
- - Inoltre l\'app è molto leggera!
-

- -

Come Funziona?

-

Quest\'app ottiene i passaggi dei bus in tempo reale filtrando i dati forniti pubblicamente sul sito www.gtt.to.it o www.5t.torino.it "per uso personale".

- -

Ingredienti:
- - Fabio Mazza attuale rockstar developer.
- - Ludovico Pavesi ex rockstar developer.
- - Valerio Bozzolan attuale manutentore.
- - Marco Gagino apprezzato ex collaboratore, ideatore icona e grafica.
- - JSoup libreria per "web scaping".
- - Google icone e libreria di supporto per il Material Design.
- - Tutti i contributori! -

- -

Licenze

-

L\'app e il relativo codice sorgente sono distribuiti sotto la licenza GNU General Public License v3+. - Ciò significa che puoi usare, studiare, migliorare e ricondividere quest\'app con qualunque mezzo e per qualsiasi scopo: a patto di mantenere sempre questi diritti a tua volta e di dare credito a Valerio Bozzolan. -

- -

Note

-

Quest\'applicazione è rilasciata nella speranza che sia utile a tutti ma senza NESSUNA garanzia.

-

Buon utilizzo! :)

- ]]> -
- Nome troppo corto, digita più caratteri e riprova - %1$s verso %2$s - %s (destinazione sconosciuta) - Errore interno inaspettato, impossibile estrarre dati dal sito GTT/5T - Visualizza sulla mappa - Non trovo un\'applicazione dove mostrarla - Posizione della fermata non trovata - - - Fermate vicine - Ricerca della posizione in corso… - Nessuna fermata nei dintorni - Preferenze - Aggiornamento del database… - Numero di fermate - Impostazioni - Impostazioni - Fermate recenti - Impostazioni generali - Gestione del database - Comincia aggiornamento manuale del database - - - Abilitare il GPS - Raggio di ricerca - Funzionalità sperimentali - arriva alle - alla fermata - Mostra arrivi - Mostra fermate - Arrivi qui vicino - Fermata rimossa dai preferiti - - -
+ + + + + Stai utilizzando l\'ultimo ritrovato in materia di rispetto della tua privacy. + Cerca + QR Code + Numero fermata + Nome fermata + Inserisci il numero della fermata + Inserisci il nome della fermata + Verifica l\'accesso ad Internet! + Sembra che nessuna fermata abbia questo nome + Errore di lettura del sito 5T/GTT (dannato sito!) + Fermata: %1$s + Linee: %1$s + Scegli la fermata… + Nessun passaggio + Nessun QR code + Preferiti + Aiuto + Informazioni + Più informazioni + News + Invia bug + Codice sorgente + Licenza + Incontra l\'autore + Fermata aggiunta ai preferiti + Impossibile aggiungere ai preferiti (memoria piena o database corrotto?)! + Preferiti + Tocca il nome di una fermata per aggiungerla in questa lista + Rimuovi + Rinomina + Rinomina fermata + Reset + Informazioni + Tocca il nome della fermata per aggiungerla ai preferiti\n\nCome leggere gli orari:\n   12:56* Orario in tempo reale\n   12:56   Orario programmato\n\nTrascina giù per aggiornare l\'orario. + OK ! + +Benvenuto! + +

Grazie per aver scelto BusTO, un\'app indipendente da GTT/5T, per spostarsi a Torino attraverso software libero:

+ +

Perché usare BusTO?

+

+ - Non sei monitorato
+ - Non ci sono pubblicità
+ - La tua privacy è al sicuro
+ - Inoltre l\'app è molto leggera!
+

+ +

Come Funziona?

+

Quest\'app ottiene i passaggi dei bus in tempo reale filtrando i dati forniti pubblicamente sul sito www.gtt.to.it o www.5t.torino.it "per uso personale".

+ +

Ingredienti:
+ - Fabio Mazza attuale rockstar developer.
+ - Ludovico Pavesi ex rockstar developer.
+ - Valerio Bozzolan attuale manutentore.
+ - Marco Gagino apprezzato ex collaboratore, ideatore icona e grafica.
+ - JSoup libreria per "web scaping".
+ - Google icone e libreria di supporto per il Material Design.
+ - Tutti i contributori! +

+ +

Licenze

+

L\'app e il relativo codice sorgente sono distribuiti sotto la licenza GNU General Public License v3+. + Ciò significa che puoi usare, studiare, migliorare e ricondividere quest\'app con qualunque mezzo e per qualsiasi scopo: a patto di mantenere sempre questi diritti a tua volta e di dare credito a Valerio Bozzolan. +

+ +

Note

+

Quest\'applicazione è rilasciata nella speranza che sia utile a tutti ma senza NESSUNA garanzia.

+

Buon utilizzo! :)

+ ]]> +
+ Nome troppo corto, digita più caratteri e riprova + %1$s verso %2$s + %s (destinazione sconosciuta) + Errore interno inaspettato, impossibile estrarre dati dal sito GTT/5T + Visualizza sulla mappa + Non trovo un\'applicazione dove mostrarla + Posizione della fermata non trovata + + + Fermate vicine + Ricerca della posizione in corso… + Nessuna fermata nei dintorni + Preferenze + Aggiornamento del database… + Numero di fermate + Impostazioni + Impostazioni + Fermate recenti + Impostazioni generali + Gestione del database + Comincia aggiornamento manuale del database + + + Abilitare il GPS + Raggio di ricerca + Funzionalità sperimentali + arriva alle + alla fermata + Mostra arrivi + Mostra fermate + Arrivi qui vicino + Fermata rimossa dai preferiti + + +
diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,120 +1,120 @@ - - - - BusTO - You\'re using the latest in technology when it comes to respecting your privacy. - - Search - Scan QR Code - Bus stop number - Bus stop name - Insert bus stop number - Insert bus stop name - %1$s towards %2$s - %s (unknown destination) - Verify your Internet connection! - Seems that no bus stop have this name - Error parsing the 5T/GTT website (damn site!) - Name too short, type more characters and retry - - Arrivals at: %1$s - Choose the bus stop… - Lines: %1$s - No timetable found - No QR code - Unexpected internal error, cannot extract data from GTT/5T website - Help - About - More about - News releases - Bug submission - Source code - Licence - Meet the author - Bus stop is now in your favorites - Bus stop removed from your favorites - Favorites - Favorites - Press on a bus stop name to populate this list - Delete - Rename - Rename the bus stop - Reset - About - - Tap the bus stop name - to add it to the favourites\n\nHow to read timelines:\n   12:56* Real-time - arrivals\n   12:56   Scheduled arrivals\n\nPull down to refresh the timetable - - GOT IT! - - Welcome! - -

Thanks for using BusTO, a "politically" independent app useful to move around Torino using a Free/Libre software.

- -

Why use this app?

-

- - You\'ll never be tracked
- - You\'ll never see boring ads
- - We\'ll always respect your privacy
- - Moreover, it\'s lightweight!
-

- -

How does it work?

-

This app will show you bus timetables gathering data from www.gtt.to.it or www.5t.torino.it "for personal use".

- -

Who worked on BusTO:
- - Fabio Mazza current rockstar developer.
- - Ludovico Pavesi previous rockstar developer.
- - Valerio Bozzolan maintainer.
- - Marco Gagino ex contributor and icon creator.
- - JSoup web scraper library.
- - makovkastar floating buttons.
- - Google Material Design icons.
- - All the contributors! -

- -

Licenses

-

The app and the related source code are released by Valerio Bozzolan under the terms of the GNU General Public License v3+). - So everyone is allowed to use, to study, to improve and to share this app by any kind of means and for any purpose: under the conditions of maintaining this rights and of attributing the original work to Valerio Bozzolan.

- -

Notes

-

This app has been developed hoping to be useful to everyone but without ANY warranty.

-

This translation is kindly provided by Riccardo Caniato and Marco Gagino.

-

Get involved! :)

- ]]> -
- Cannot add to favorites (storage full or corrupted database?)! - View on a map - Cannot find any application to show it in - Cannot find the position of the stop - - ListFragment - BusTO - it.reyboz.bustorino.preferences - db_is_updating - - Nearby stops - Nearby connections - - Finding the position… - No stops nearby - Number of stops - Preferences - Settings - Settings - Experimental features - Search radius - Recent stops - General settings - Database management - Launch manual database update - - Please enable GPS - Database update in progress… - is arriving at - at the stop - %1$s - %2$s - Show arrivals - Show stops -
+ + + + BusTO + You\'re using the latest in technology when it comes to respecting your privacy. + + Search + Scan QR Code + Bus stop number + Bus stop name + Insert bus stop number + Insert bus stop name + %1$s towards %2$s + %s (unknown destination) + Verify your Internet connection! + Seems that no bus stop have this name + Error parsing the 5T/GTT website (damn site!) + Name too short, type more characters and retry + + Arrivals at: %1$s + Choose the bus stop… + Lines: %1$s + No timetable found + No QR code + Unexpected internal error, cannot extract data from GTT/5T website + Help + About + More about + News releases + Bug submission + Source code + Licence + Meet the author + Bus stop is now in your favorites + Bus stop removed from your favorites + Favorites + Favorites + Press on a bus stop name to populate this list + Delete + Rename + Rename the bus stop + Reset + About + + Tap the bus stop name + to add it to the favourites\n\nHow to read timelines:\n   12:56* Real-time + arrivals\n   12:56   Scheduled arrivals\n\nPull down to refresh the timetable + + GOT IT! + + Welcome! + +

Thanks for using BusTO, a "politically" independent app useful to move around Torino using a Free/Libre software.

+ +

Why use this app?

+

+ - You\'ll never be tracked
+ - You\'ll never see boring ads
+ - We\'ll always respect your privacy
+ - Moreover, it\'s lightweight!
+

+ +

How does it work?

+

This app will show you bus timetables gathering data from www.gtt.to.it or www.5t.torino.it "for personal use".

+ +

Who worked on BusTO:
+ - Fabio Mazza current rockstar developer.
+ - Ludovico Pavesi previous rockstar developer.
+ - Valerio Bozzolan maintainer.
+ - Marco Gagino ex contributor and icon creator.
+ - JSoup web scraper library.
+ - makovkastar floating buttons.
+ - Google Material Design icons.
+ - All the contributors! +

+ +

Licenses

+

The app and the related source code are released by Valerio Bozzolan under the terms of the GNU General Public License v3+). + So everyone is allowed to use, to study, to improve and to share this app by any kind of means and for any purpose: under the conditions of maintaining this rights and of attributing the original work to Valerio Bozzolan.

+ +

Notes

+

This app has been developed hoping to be useful to everyone but without ANY warranty.

+

This translation is kindly provided by Riccardo Caniato and Marco Gagino.

+

Get involved! :)

+ ]]> +
+ Cannot add to favorites (storage full or corrupted database?)! + View on a map + Cannot find any application to show it in + Cannot find the position of the stop + + ListFragment - BusTO + it.reyboz.bustorino.preferences + db_is_updating + + Nearby stops + Nearby connections + + Finding the position… + No stops nearby + Number of stops + Preferences + Settings + Settings + Experimental features + Search radius + Recent stops + General settings + Database management + Launch manual database update + + Please enable GPS + Database update in progress… + is arriving at + at the stop + %1$s - %2$s + Show arrivals + Show stops +
diff --git a/src/it/reyboz/bustorino/ActivityFavorites.java b/src/it/reyboz/bustorino/ActivityFavorites.java --- a/src/it/reyboz/bustorino/ActivityFavorites.java +++ b/src/it/reyboz/bustorino/ActivityFavorites.java @@ -1,302 +1,302 @@ -/* - BusTO - Arrival times for Turin public transports. - Copyright (C) 2014 Valerio Bozzolan - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino; - -import android.database.Cursor; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.widget.*; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.adapters.StopAdapter; -import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction; -import it.reyboz.bustorino.middleware.StopsDB; -import it.reyboz.bustorino.middleware.UserDB; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.Uri; -import android.os.AsyncTask; -import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.content.Intent; -import android.database.sqlite.SQLiteDatabase; -import android.os.Bundle; - -import java.util.List; - -public class ActivityFavorites extends AppCompatActivity implements LoaderManager.LoaderCallbacks { - private ListView favoriteListView; - private SQLiteDatabase userDB; - private EditText bus_stop_name; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_favorites); - - // this should be done in onStarted and closed in onStop, but apparently onStarted is never run. - this.userDB = new UserDB(getApplicationContext()).getWritableDatabase(); - - ActionBar ab = getSupportActionBar(); - assert ab != null; - ab.setIcon(R.drawable.ic_launcher); - ab.setDisplayHomeAsUpEnabled(true); // Back button - - favoriteListView = (ListView) findViewById(R.id.favoriteListView); - - createFavoriteList(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - if (v.getId() == R.id.favoriteListView) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_favourites_entry, menu); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - // Respond to the action bar's Up/Home button - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item - .getMenuInfo(); - - Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position); - - switch (item.getItemId()) { - case R.id.action_favourite_entry_delete: - - // remove the stop from the favorites in background - new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE) { - - /** - * Callback fired when everything was done - * - * @param result - */ - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - - // update the favorite list - createFavoriteList(); - } - }.execute(busStop); - - return true; - case R.id.action_rename_bus_stop_username: - showBusStopUsernameInputDialog(busStop); - return true; - case R.id.action_view_on_map: - final String theGeoUrl = busStop.getGeoURL(); - if(theGeoUrl==null){ - //doesn't have a position - Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_position,Toast.LENGTH_SHORT).show(); - return true; - } - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(theGeoUrl)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if(intent.resolveActivity(getPackageManager())!=null) - startActivity(intent); - else { - Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_activity,Toast.LENGTH_SHORT).show(); - } - return true; - default: - return super.onContextItemSelected(item); - } - } - - void createFavoriteList() { - // TODO: memoize default list, query only user names every time? - new AsyncGetFavorites(getApplicationContext(), this.userDB).execute(); - } - - public void showBusStopUsernameInputDialog(final Stop busStop) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - LayoutInflater inflater = this.getLayoutInflater(); - View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null); - - bus_stop_name = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name); - bus_stop_name.setText(busStop.getStopDisplayName()); - bus_stop_name.setHint(busStop.getStopDefaultName()); - - builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title)); - builder.setView(renameDialogLayout); - builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String busStopUsername = bus_stop_name.getText().toString(); - String oldUserName = busStop.getStopUserName(); - - // changed to none - if(busStopUsername.length() == 0) { - // unless it was already empty, set new - if(oldUserName != null) { - busStop.setStopUserName(null); - UserDB.updateStop(busStop, userDB); - createFavoriteList(); - } - } else { // changed to something - // something different? - if(oldUserName == null || !busStopUsername.equals(oldUserName)) { - busStop.setStopUserName(busStopUsername); - UserDB.updateStop(busStop, userDB); - createFavoriteList(); - } - } - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // delete user name from database - busStop.setStopUserName(null); - UserDB.updateStop(busStop, userDB); - - createFavoriteList(); - } - }); - builder.show(); - } - - /** - * This one runs. onStart instead gets ignored for no reason whatsoever. - * - * @see Android Activity Lifecycle - */ - @Override - protected void onStop() { - super.onStop(); - this.userDB.close(); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return null; - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - - } - - @Override - public void onLoaderReset(Loader loader) { - - } - - private class AsyncGetFavorites extends AsyncTask> { - private Context c; - private SQLiteDatabase userDB; - - AsyncGetFavorites(Context c, SQLiteDatabase userDB) { - this.c = c; - this.userDB = userDB; - } - - @Override - protected List doInBackground(Void... voids) { - StopsDB stopsDB = new StopsDB(c); - stopsDB.openIfNeeded(); - List busStops = UserDB.getFavorites(this.userDB, stopsDB); - stopsDB.closeIfNeeded(); - - return busStops; - } - - @Override - protected void onPostExecute(List busStops) { - // If no data is found show a friendly message - if (busStops.size() == 0) { - favoriteListView.setVisibility(View.INVISIBLE); - TextView favoriteTipTextView = (TextView) findViewById(R.id.favoriteTipTextView); - assert favoriteTipTextView != null; - favoriteTipTextView.setVisibility(View.VISIBLE); - } - - /* There's a nice method called notifyDataSetChanged() to avoid building the ListView - * all over again. This method exists in a billion answers on Stack Overflow, but - * it's nowhere to be seen around here, Android Studio can't find it no matter what. - * Anyway, it only works from Android 2.3 onward (which is why it refuses to appear, I - * guess) and requires to modify the list with .add() and .clear() and some other - * methods, so to update a single stop we need to completely rebuild the list for no - * reason. It would probably end up as "slow" as throwing away the old ListView and - * redrwaing everything. - */ - - // Show results - favoriteListView.setAdapter(new StopAdapter(this.c, busStops)); - favoriteListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View view, int position, long id) { - /** - * Casting because of Javamerda - * @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener - */ - Stop busStop = (Stop) parent.getItemAtPosition(position); - - Intent intent = new Intent(ActivityFavorites.this, - ActivityMain.class); - - Bundle b = new Bundle(); - // TODO: is passing a serialized object a good idea? Or rather, is it reasonably fast? - //b.putSerializable("bus-stop-serialized", busStop); - b.putString("bus-stop-ID", busStop.ID); - b.putString("bus-stop-display-name", busStop.getStopDisplayName()); - intent.putExtras(b); - //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - // Intent.FLAG_ACTIVITY_CLEAR_TASK isn't supported in API < 11 and we're targeting API 7... - intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - - startActivity(intent); - - finish(); - } - }); - registerForContextMenu(favoriteListView); - } - } -} +/* + BusTO - Arrival times for Turin public transports. + Copyright (C) 2014 Valerio Bozzolan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino; + +import android.database.Cursor; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.widget.*; +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.adapters.StopAdapter; +import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction; +import it.reyboz.bustorino.middleware.StopsDB; +import it.reyboz.bustorino.middleware.UserDB; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.AsyncTask; +import android.support.v4.app.NavUtils; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; + +import java.util.List; + +public class ActivityFavorites extends AppCompatActivity implements LoaderManager.LoaderCallbacks { + private ListView favoriteListView; + private SQLiteDatabase userDB; + private EditText bus_stop_name; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_favorites); + + // this should be done in onStarted and closed in onStop, but apparently onStarted is never run. + this.userDB = new UserDB(getApplicationContext()).getWritableDatabase(); + + ActionBar ab = getSupportActionBar(); + assert ab != null; + ab.setIcon(R.drawable.ic_launcher); + ab.setDisplayHomeAsUpEnabled(true); // Back button + + favoriteListView = (ListView) findViewById(R.id.favoriteListView); + + createFavoriteList(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if (v.getId() == R.id.favoriteListView) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_favourites_entry, menu); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // Respond to the action bar's Up/Home button + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + .getMenuInfo(); + + Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position); + + switch (item.getItemId()) { + case R.id.action_favourite_entry_delete: + + // remove the stop from the favorites in background + new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE) { + + /** + * Callback fired when everything was done + * + * @param result + */ + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + // update the favorite list + createFavoriteList(); + } + }.execute(busStop); + + return true; + case R.id.action_rename_bus_stop_username: + showBusStopUsernameInputDialog(busStop); + return true; + case R.id.action_view_on_map: + final String theGeoUrl = busStop.getGeoURL(); + if(theGeoUrl==null){ + //doesn't have a position + Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_position,Toast.LENGTH_SHORT).show(); + return true; + } + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(theGeoUrl)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if(intent.resolveActivity(getPackageManager())!=null) + startActivity(intent); + else { + Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_activity,Toast.LENGTH_SHORT).show(); + } + return true; + default: + return super.onContextItemSelected(item); + } + } + + void createFavoriteList() { + // TODO: memoize default list, query only user names every time? + new AsyncGetFavorites(getApplicationContext(), this.userDB).execute(); + } + + public void showBusStopUsernameInputDialog(final Stop busStop) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + LayoutInflater inflater = this.getLayoutInflater(); + View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null); + + bus_stop_name = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name); + bus_stop_name.setText(busStop.getStopDisplayName()); + bus_stop_name.setHint(busStop.getStopDefaultName()); + + builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title)); + builder.setView(renameDialogLayout); + builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String busStopUsername = bus_stop_name.getText().toString(); + String oldUserName = busStop.getStopUserName(); + + // changed to none + if(busStopUsername.length() == 0) { + // unless it was already empty, set new + if(oldUserName != null) { + busStop.setStopUserName(null); + UserDB.updateStop(busStop, userDB); + createFavoriteList(); + } + } else { // changed to something + // something different? + if(oldUserName == null || !busStopUsername.equals(oldUserName)) { + busStop.setStopUserName(busStopUsername); + UserDB.updateStop(busStop, userDB); + createFavoriteList(); + } + } + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // delete user name from database + busStop.setStopUserName(null); + UserDB.updateStop(busStop, userDB); + + createFavoriteList(); + } + }); + builder.show(); + } + + /** + * This one runs. onStart instead gets ignored for no reason whatsoever. + * + * @see Android Activity Lifecycle + */ + @Override + protected void onStop() { + super.onStop(); + this.userDB.close(); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return null; + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + private class AsyncGetFavorites extends AsyncTask> { + private Context c; + private SQLiteDatabase userDB; + + AsyncGetFavorites(Context c, SQLiteDatabase userDB) { + this.c = c; + this.userDB = userDB; + } + + @Override + protected List doInBackground(Void... voids) { + StopsDB stopsDB = new StopsDB(c); + stopsDB.openIfNeeded(); + List busStops = UserDB.getFavorites(this.userDB, stopsDB); + stopsDB.closeIfNeeded(); + + return busStops; + } + + @Override + protected void onPostExecute(List busStops) { + // If no data is found show a friendly message + if (busStops.size() == 0) { + favoriteListView.setVisibility(View.INVISIBLE); + TextView favoriteTipTextView = (TextView) findViewById(R.id.favoriteTipTextView); + assert favoriteTipTextView != null; + favoriteTipTextView.setVisibility(View.VISIBLE); + } + + /* There's a nice method called notifyDataSetChanged() to avoid building the ListView + * all over again. This method exists in a billion answers on Stack Overflow, but + * it's nowhere to be seen around here, Android Studio can't find it no matter what. + * Anyway, it only works from Android 2.3 onward (which is why it refuses to appear, I + * guess) and requires to modify the list with .add() and .clear() and some other + * methods, so to update a single stop we need to completely rebuild the list for no + * reason. It would probably end up as "slow" as throwing away the old ListView and + * redrwaing everything. + */ + + // Show results + favoriteListView.setAdapter(new StopAdapter(this.c, busStops)); + favoriteListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + /** + * Casting because of Javamerda + * @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener + */ + Stop busStop = (Stop) parent.getItemAtPosition(position); + + Intent intent = new Intent(ActivityFavorites.this, + ActivityMain.class); + + Bundle b = new Bundle(); + // TODO: is passing a serialized object a good idea? Or rather, is it reasonably fast? + //b.putSerializable("bus-stop-serialized", busStop); + b.putString("bus-stop-ID", busStop.ID); + b.putString("bus-stop-display-name", busStop.getStopDisplayName()); + intent.putExtras(b); + //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + // Intent.FLAG_ACTIVITY_CLEAR_TASK isn't supported in API < 11 and we're targeting API 7... + intent.setFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + + startActivity(intent); + + finish(); + } + }); + registerForContextMenu(favoriteListView); + } + } +} diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java --- a/src/it/reyboz/bustorino/ActivityMain.java +++ b/src/it/reyboz/bustorino/ActivityMain.java @@ -1,877 +1,903 @@ -/* - BusTO - Arrival times for Turin public transports. - Copyright (C) 2014 Valerio Bozzolan - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.sqlite.SQLiteDatabase; -import android.location.*; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.NavUtils; -import android.support.v4.widget.SwipeRefreshLayout; -import android.util.Log; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.*; - -import com.google.zxing.integration.android.IntentIntegrator; -import com.google.zxing.integration.android.IntentResult; -import android.support.design.widget.FloatingActionButton; - -import it.reyboz.bustorino.backend.*; -import it.reyboz.bustorino.fragments.*; -import it.reyboz.bustorino.middleware.*; - -import java.util.List; - -public class ActivityMain extends GeneralActivity implements FragmentListener { - - /* - * Layout elements - */ - private EditText busStopSearchByIDEditText; - private EditText busStopSearchByNameEditText; - private ProgressBar progressBar; - private TextView howDoesItWorkTextView; - private Button hideHintButton; - private MenuItem actionHelpMenuItem; - private SwipeRefreshLayout swipeRefreshLayout; - private FloatingActionButton floatingActionButton; - private FragmentManager framan; - private Snackbar snackbar; - - /* - * Search mode - */ - private static final int SEARCH_BY_NAME = 0; - private static final int SEARCH_BY_ID = 1; - private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12 - private int searchMode; - - /* - * Options - */ - private final String OPTION_SHOW_LEGEND = "show_legend"; - private final String LOCATION_PERMISSION_GIVEN = "loc_permission"; - /* - * Status - */ - private DBStatusManager prefsManager; - private DBStatusManager.OnDBUpdateStatusChangeListener updatelistener; - private static final String DEBUG_TAG="BusTO - MainActivity"; - /* // useful for testing: - public class MockFetcher implements ArrivalsFetcher { - @Override - public Palina ReadArrivalTimesAll(String routeID, AtomicReference res) { - SystemClock.sleep(5000); - res.set(result.SERVER_ERROR); - return new Palina(); - } - } - private ArrivalsFetcher[] ArrivalFetchers = {new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher()};*/ - - private RecursionHelper ArrivalFetchersRecursionHelper = new RecursionHelper<>(new ArrivalsFetcher[] {new GTTJSONFetcher(), new FiveTScraperFetcher()}); - private RecursionHelper StopsFindersByNameRecursionHelper = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()}); - /* - * Position - */ - //Fine location criteria - private final Criteria cr = new Criteria(); - private boolean pendingNearbyStopsRequest = false; - private LocationManager locmgr; - /* - * Database Access - */ - private StopsDB stopsDB; - private UserDB userDB; - private FragmentHelper fh; - - ///////////////////////////////// EVENT HANDLERS /////////////////////////////////////////////// - - /* - * @see swipeRefreshLayout - */ - private Handler handler = new Handler(); - private final Runnable refreshing = new Runnable() { - public void run() { - if(framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment){ - ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame); - String stopName = fragment.getStopID(); - new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(stopName); - } else - new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); - } - }; - - //// MAIN METHOD /// - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - framan = getSupportFragmentManager(); - this.stopsDB = new StopsDB(getApplicationContext()); - this.userDB = new UserDB(getApplicationContext()); - setContentView(R.layout.activity_main); - busStopSearchByIDEditText = (EditText) findViewById(R.id.busStopSearchByIDEditText); - busStopSearchByNameEditText = (EditText) findViewById(R.id.busStopSearchByNameEditText); - progressBar = (ProgressBar) findViewById(R.id.progressBar); - howDoesItWorkTextView = (TextView) findViewById(R.id.howDoesItWorkTextView); - hideHintButton = (Button) findViewById(R.id.hideHintButton); - swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.listRefreshLayout); - floatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionButton); - - framan.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { - @Override - public void onBackStackChanged() { - Log.d("MainActivity, BusTO", "BACK STACK CHANGED"); - } - }); - - busStopSearchByIDEditText.setSelectAllOnFocus(true); - busStopSearchByIDEditText - .setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, - KeyEvent event) { - // IME_ACTION_SEARCH alphabetical option - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - onSearchClick(v); - return true; - } - return false; - } - }); - busStopSearchByNameEditText - .setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, - KeyEvent event) { - // IME_ACTION_SEARCH alphabetical option - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - onSearchClick(v); - return true; - } - return false; - } - }); - - // Called when the layout is pulled down - swipeRefreshLayout - .setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - handler.post(refreshing); - } - }); - - /** - * @author Marco Gagino!!! - */ - //swipeRefreshLayout.setColorSchemeColors(R.color.blue_500, R.color.orange_500); // setColorScheme is deprecated, setColorSchemeColors isn't - swipeRefreshLayout.setColorSchemeResources(R.color.blue_500,R.color.orange_500); - fh = new FragmentHelper(this,R.id.listRefreshLayout,R.id.resultFrame); - setSearchModeBusStopID(); - - //---------------------------- START INTENT CHECK QUEUE ------------------------------------ - - // Intercept calls from URL intent - boolean tryedFromIntent = false; - - String busStopID = null; - String busStopDisplayName = null; - Uri data = getIntent().getData(); - if (data != null) { - busStopID = getBusStopIDFromUri(data); - tryedFromIntent = true; - } - - // Intercept calls from other activities - if (!tryedFromIntent) { - Bundle b = getIntent().getExtras(); - if (b != null) { - busStopID = b.getString("bus-stop-ID"); - busStopDisplayName = b.getString("bus-stop-display-name"); - - /** - * I'm not very sure if you are coming from an Intent. - * Some launchers work in strange ways. - */ - tryedFromIntent = busStopID != null; - } - } - - //---------------------------- END INTENT CHECK QUEUE -------------------------------------- - - if (busStopID == null) { - // Show keyboard if can't start from intent - // JUST DON'T - // showKeyboard(); - - // You haven't obtained anything... from an intent? - if (tryedFromIntent) { - - // This shows a luser warning - ArrivalFetchersRecursionHelper.reset(); - Toast.makeText(getApplicationContext(), - R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show(); - } - } else { - // If you are here an intent has worked successfully - setBusStopSearchByIDEditText(busStopID); - /* - //THIS PART SHOULDN'T BE NECESSARY SINCE THE LAST SUCCESSFULLY SEARCHED BUS - // STOP IS ADDED AUTOMATICALLY - Stop nextStop = new Stop(busStopID); - // forcing it as user name even though it could be standard name, it doesn't really matter - nextStop.setStopUserName(busStopDisplayName); - //set stop as last succe - fh.setLastSuccessfullySearchedBusStop(nextStop); - */ - createFragmentForStop(busStopID); - } - //Try (hopefully) database update - //TODO: Start the service in foreground, check last time it ran before - DatabaseUpdateService.startDBUpdate(getApplicationContext()); - /* - Set database update - */ - updatelistener = new DBStatusManager.OnDBUpdateStatusChangeListener(){ - @Override - public boolean defaultStatusValue() { - return true; - } - - @Override - public void onDBStatusChanged(boolean updating) { - - if(updating){ - createDefaultSnackbar(); - } - else if(snackbar!=null){ - snackbar.dismiss(); - snackbar = null; - } - - - } - }; - prefsManager = new DBStatusManager(getApplicationContext(),updatelistener); - prefsManager.registerListener(); - - //locationHandler = new GPSLocationAdapter(getApplicationContext()); - //--------- NEARBY STOPS--------// - //SETUP LOCATION - locmgr = (LocationManager) getSystemService(LOCATION_SERVICE); - cr.setAccuracy(Criteria.ACCURACY_FINE); - cr.setAltitudeRequired(false); - cr.setBearingRequired(false); - cr.setCostAllowed(true); - cr.setPowerRequirement(Criteria.NO_REQUIREMENT); - //We want the nearby bus stops! - handler.post(new NearbyStopsRequester()); - //If there are no providers available, then, wait for them - - - Log.d("MainActivity", "Created"); - - } - - /** - * Reload bus stop timetable when it's fulled resumed from background. - */ - /** - @Override - protected void onPostResume() { - super.onPostResume(); - Log.d("ActivityMain", "onPostResume fired. Last successfully bus stop ID: " + fh.getLastSuccessfullySearchedBusStop()); - if (searchMode == SEARCH_BY_ID && fh.getLastSuccessfullySearchedBusStop() != null) { - setBusStopSearchByIDEditText(fh.getLastSuccessfullySearchedBusStop().ID); - //new asyncWgetBusStopFromBusStopID(lastSuccessfullySearchedBusStop.ID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); - new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); - } else { - //we have new activity or we don't have a new searched stop. - //Let's search stops nearby - LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE); - Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.resultFrame); - - - } - //show the FAB since it remains hidden - floatingActionButton.show(); - - } - **/ - - - @Override - protected void onPause() { - super.onPause(); - fh.stopLastRequestIfNeeded(); - fh.setBlockAllActivities(true); - if(updatelistener!=null && prefsManager!=null) prefsManager.unregisterListener(); - locmgr.removeUpdates(locListener); - } - - @Override - protected void onResume() { - super.onResume(); - fh.setBlockAllActivities(false); - if(updatelistener!=null && prefsManager!=null) { - prefsManager.registerListener(); - if(prefsManager.isDBUpdating(true)){ - createDefaultSnackbar(); - } - } - if(pendingNearbyStopsRequest) - handler.post(new NearbyStopsRequester()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.main, menu); - - actionHelpMenuItem = menu.findItem(R.id.action_help); - return true; - } - - /** - * Callback fired when a MenuItem is selected - * - * @param item - * @return - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - switch (item.getItemId()) { - case android.R.id.home: - // Respond to the action bar's Up/Home button - NavUtils.navigateUpFromSameTask(this); - return true; - case R.id.action_help: - showHints(); - return true; - case R.id.action_favorites: - startActivity(new Intent(ActivityMain.this, ActivityFavorites.class)); - return true; - case R.id.action_about: - startActivity(new Intent(ActivityMain.this, ActivityAbout.class)); - return true; - case R.id.action_news: - openIceweasel("https://gitpull.it/w/librebusto/#how-to-get-news"); - return true; - case R.id.action_bugs: - openIceweasel("https://gitpull.it/w/librebusto/#how-to-create-a-bug-feature"); - return true; - case R.id.action_source: - openIceweasel("https://gitpull.it/w/librebusto/#how-to-hack-busto"); - return true; - case R.id.action_licence: - openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html"); - return true; - case R.id.action_settings: - Log.d("MAINBusTO","Pressed button preferences"); - startActivity(new Intent(ActivityMain.this,ActivitySettings.class)); - } - return super.onOptionsItemSelected(item); - } - - /** - * OK this is pure shit - * - * @param v View clicked - */ - public void onSearchClick(View v) { - if (searchMode == SEARCH_BY_ID) { - String busStopID = busStopSearchByIDEditText.getText().toString(); - //OLD ASYNCTASK - //new asyncWgetBusStopFromBusStopID(busStopID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); - - if(busStopID == null || busStopID.length() <= 0) { - showMessage(R.string.insert_bus_stop_number_error); - toggleSpinner(false); - } else{ - new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(busStopID); - Log.d("MainActiv","Started search for arrivals of stop "+busStopID); - } - } else { // searchMode == SEARCH_BY_NAME - String query = busStopSearchByNameEditText.getText().toString(); - //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); - new AsyncDataDownload(AsyncDataDownload.RequestType.STOPS,fh).execute(query); - } - } - /** PERMISSION STUFF **/ - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - switch (requestCode){ - case PERMISSION_REQUEST_POSITION: - if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ - setOption(LOCATION_PERMISSION_GIVEN,true); - //if we sent a request for a new NearbyStopsFragment - if(pendingNearbyStopsRequest){ - pendingNearbyStopsRequest=false; - handler.post(new NearbyStopsRequester()); - } - - } else { - //permission denied - setOption(LOCATION_PERMISSION_GIVEN,false); - } - //add other cases for permissions - } - - } - - - @Override - public void createFragmentForStop(String ID) { - //new asyncWgetBusStopFromBusStopID(ID, ArrivalFetchersRecursionHelper,lastSuccessfullySearchedBusStop); - if(ID == null || ID.length() <= 0) { - // we're still in UI thread, no need to mess with Progress - showMessage(R.string.insert_bus_stop_number_error); - toggleSpinner(false); - } else { - new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(ID); - Log.d("MainActiv","Started search for arrivals of stop "+ID); - } - } - - - - /** - * QR scan button clicked - * - * @param v View QRButton clicked - */ - public void onQRButtonClick(View v) { - IntentIntegrator integrator = new IntentIntegrator(this); - integrator.initiateScan(); - } - - /** - * Receive the Barcode Scanner Intent - * - */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); - - Uri uri; - try { - uri = Uri.parse(scanResult != null ? scanResult.getContents() : null); // this apparently prevents NullPointerException. Somehow. - } catch (NullPointerException e) { - Toast.makeText(getApplicationContext(), - R.string.no_qrcode, Toast.LENGTH_SHORT).show(); - return; - } - - String busStopID = getBusStopIDFromUri(uri); - busStopSearchByIDEditText.setText(busStopID); - createFragmentForStop(busStopID); - } - - public void onHideHint(View v) { - hideHints(); - setOption(OPTION_SHOW_LEGEND, false); - } - - public void onToggleKeyboardLayout(View v) { - if (searchMode == SEARCH_BY_NAME) { - setSearchModeBusStopID(); - if (busStopSearchByIDEditText.requestFocus()) { - showKeyboard(); - } - } else { // searchMode == SEARCH_BY_ID - setSearchModeBusStopName(); - if (busStopSearchByNameEditText.requestFocus()) { - showKeyboard(); - } - } - } - private void createDefaultSnackbar(){ - if(snackbar==null){ - snackbar = Snackbar.make(findViewById(R.id.searchButton),R.string.database_update_message,Snackbar.LENGTH_INDEFINITE); - } - snackbar.show(); - } - ///////////////////////////////// POSITION STUFF////////////////////////////////////////////// - - private void resolveStopRequest(String provider){ - Log.d(DEBUG_TAG,"Provider "+provider+" got enabled"); - if(locmgr!=null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)){ - pendingNearbyStopsRequest = false; - handler.post(new NearbyStopsRequester()); - } - } - final LocationListener locListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - Log.d(DEBUG_TAG,"Location changed"); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - Log.d(DEBUG_TAG,"Location provider status: "+status); - if(status== LocationProvider.AVAILABLE){ - resolveStopRequest(provider); - } - } - - @Override - public void onProviderEnabled(String provider) { - resolveStopRequest(provider); - } - - @Override - public void onProviderDisabled(String provider) { - - } - }; - - class NearbyStopsRequester implements Runnable{ - @SuppressLint("MissingPermission") - @Override - public void run() { - final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false); - - if(!canRunPosition){ - pendingNearbyStopsRequest = true; - assertLocationPermissions(); - return; - } else setOption(LOCATION_PERMISSION_GIVEN,true); - - LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE); - if(locManager == null) { - Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment"); - return; - } - if(anyLocationProviderMatchesCriteria(locManager,cr,true) && fh.getLastSuccessfullySearchedBusStop()==null) { - //Go ahead with the request - Log.d("mainActivity","Recreating stop fragment"); - swipeRefreshLayout.setVisibility(View.VISIBLE); - NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS); - Fragment oldFrag = framan.findFragmentById(R.id.resultFrame); - FragmentTransaction ft = framan.beginTransaction(); - if(oldFrag!=null) - ft.remove(oldFrag); - ft.add(R.id.resultFrame,fragment,"nearbyStop_correct"); - ft.commit(); - framan.executePendingTransactions(); - pendingNearbyStopsRequest = false; - } else if(!anyLocationProviderMatchesCriteria(locManager,cr,true)){ - //Wait for the providers - Log.d(DEBUG_TAG,"Queuing position request"); - pendingNearbyStopsRequest = true; - locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,10,0.1f,locListener); - } - - } - } - - private boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled){ - List providers = mng.getProviders(cr,enabled); - Log.d(DEBUG_TAG,"Getting enabled location providers: "); - for(String s: providers){ - Log.d(DEBUG_TAG,"Provider "+s); - } - return providers.size()>0; - } - - - - ///////////////////////////////// OTHER STUFF ////////////////////////////////////////////////// - - /** - * Get the last successfully searched bus stop or NULL - * - * @return - */ - @Override - public Stop getLastSuccessfullySearchedBusStop() { - return fh.getLastSuccessfullySearchedBusStop(); - } - - /** - * Get the last successfully searched bus stop ID or NULL - * - * @return - */ - @Override - public String getLastSuccessfullySearchedBusStopID() { - Stop stop = getLastSuccessfullySearchedBusStop(); - return stop == null ? null : stop.ID; - } - - /** - * Update the star "Add to favorite" icon - */ - @Override - public void updateStarIconFromLastBusStop() { - // check if there is a last Stop - String stopID = getLastSuccessfullySearchedBusStopID(); - if(stopID == null) { - // TODO: hide the star - } else { - // filled or outline? - if(isStopInFavorites(stopID)) { - // TODO: fill star - } else { - // TODO: outline star - } - - // TODO: show the star - } - } - - /** - * Check if the last Bus Stop is in the favorites - * - * @return - */ - public boolean isStopInFavorites(String busStopId) { - boolean found = false; - - // no stop no party - if(busStopId != null) { - SQLiteDatabase userDB = new UserDB(getApplicationContext()).getReadableDatabase(); - found = UserDB.isStopInFavorites(userDB, busStopId); - } - - return found; - } - - /** - * Add the last Stop to favorites - */ - @Override - public void toggleLastStopToFavorites() { - Stop stop = getLastSuccessfullySearchedBusStop(); - if(stop != null) { - - // toggle the status in background - new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE) { - - /** - * Callback fired when the Stop is saved in the favorites - * @param result - */ - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - - // update the star icon - updateStarIconFromLastBusStop(); - } - - }.execute(stop); - } else { - // this case have no sense, but just immediately update the favorite icon - updateStarIconFromLastBusStop(); - } - } - - @Override - public void showFloatingActionButton(boolean yes) { - if(yes) floatingActionButton.show(); - else floatingActionButton.hide(); - } - - @Override - public void enableRefreshLayout(boolean yes) { - swipeRefreshLayout.setEnabled(yes); - } - - ////////////////////////////////////// GUI HELPERS ///////////////////////////////////////////// - @Override - public void showKeyboard() { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText; - imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); - } - - @Override - public void showMessage(int messageID) { - Toast.makeText(getApplicationContext(), messageID, Toast.LENGTH_SHORT).show(); - } - - private void setSearchModeBusStopID() { - searchMode = SEARCH_BY_ID; - busStopSearchByNameEditText.setVisibility(View.GONE); - busStopSearchByNameEditText.setText(""); - busStopSearchByIDEditText.setVisibility(View.VISIBLE); - floatingActionButton.setImageResource(R.drawable.alphabetical); - } - - private void setSearchModeBusStopName() { - searchMode = SEARCH_BY_NAME; - busStopSearchByIDEditText.setVisibility(View.GONE); - busStopSearchByIDEditText.setText(""); - busStopSearchByNameEditText.setVisibility(View.VISIBLE); - floatingActionButton.setImageResource(R.drawable.numeric); - } - - /** - * Having that cursor at the left of the edit text makes me cancer. - * @param busStopID bus stop ID - */ - private void setBusStopSearchByIDEditText(String busStopID) { - busStopSearchByIDEditText.setText(busStopID); - busStopSearchByIDEditText.setSelection(busStopID.length()); - } - - private void showHints() { - howDoesItWorkTextView.setVisibility(View.VISIBLE); - hideHintButton.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(false); - } - - private void hideHints() { - howDoesItWorkTextView.setVisibility(View.GONE); - hideHintButton.setVisibility(View.GONE); - actionHelpMenuItem.setVisible(true); - } - - //TODO: toggle spinner from mainActivity - @Override - public void toggleSpinner(boolean enable) { - if (enable) { - //already set by the RefreshListener when needed - //swipeRefreshLayout.setRefreshing(true); - progressBar.setVisibility(View.VISIBLE); - } else { - swipeRefreshLayout.setRefreshing(false); - progressBar.setVisibility(View.GONE); - } - } - - private void prepareGUIForBusLines() { - swipeRefreshLayout.setEnabled(true); - swipeRefreshLayout.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(true); - } - - private void prepareGUIForBusStops() { - swipeRefreshLayout.setEnabled(false); - swipeRefreshLayout.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(false); - } - - - /** - * This provides a temporary fix to make the transition - * to a single asynctask go smoother - * @param fragmentType the type of fragment created - */ - @Override - public void readyGUIfor(FragmentKind fragmentType) { - hideKeyboard(); - //if we are getting results, already, stop waiting for nearbyStops - if(pendingNearbyStopsRequest && (fragmentType==FragmentKind.ARRIVALS || fragmentType==FragmentKind.STOPS)) { - locmgr.removeUpdates(locListener); - pendingNearbyStopsRequest = false; - } - if(fragmentType==null) Log.e("ActivityMain","Problem with fragmentType"); - else - switch (fragmentType){ - case ARRIVALS: - prepareGUIForBusLines(); - if (getOption(OPTION_SHOW_LEGEND, true)) { - showHints(); - } - break; - case STOPS: - prepareGUIForBusStops(); - break; - default: - Log.e("BusTO Activity","Called readyGUI with unsupported type of Fragment"); - return; - } - // Shows hints - - } - - /** - * Open an URL in the default browser. - * - * @param url URL - */ - public void openIceweasel(String url) { - Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent1); - } - - ///////////////////// INTENT HELPER //////////////////////////////////////////////////////////// - - /** - * Try to extract the bus stop ID from a URi - * - * @param uri The URL - * @return bus stop ID or null - */ - public static String getBusStopIDFromUri(Uri uri) { - String busStopID; - - // everithing catches fire when passing null to a switch. - String host = uri.getHost(); - if(host == null) { - Log.e("ActivityMain", "Not an URL: " + uri); - return null; - } - - switch(host) { - case "m.gtt.to.it": - // http://m.gtt.to.it/m/it/arrivi.jsp?n=1254 - busStopID = uri.getQueryParameter("n"); - if (busStopID == null) { - Log.e("ActivityMain", "Expected ?n from: " + uri); - } - break; - case "www.gtt.to.it": - case "gtt.to.it": - // http://www.gtt.to.it/cms/percorari/arrivi?palina=1254 - busStopID = uri.getQueryParameter("palina"); - if (busStopID == null) { - Log.e("ActivityMain", "Expected ?palina from: " + uri); - } - break; - default: - Log.e("ActivityMain", "Unexpected intent URL: " + uri); - busStopID = null; - } - return busStopID; - } - - - +/* + BusTO - Arrival times for Turin public transports. + Copyright (C) 2014 Valerio Bozzolan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.sqlite.SQLiteDatabase; +import android.location.*; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.NavUtils; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; +import android.support.design.widget.FloatingActionButton; + +import it.reyboz.bustorino.backend.*; +import it.reyboz.bustorino.fragments.*; +import it.reyboz.bustorino.middleware.*; + +import java.util.List; + +public class ActivityMain extends GeneralActivity implements FragmentListener { + + /* + * Layout elements + */ + private EditText busStopSearchByIDEditText; + private EditText busStopSearchByNameEditText; + private ProgressBar progressBar; + private TextView howDoesItWorkTextView; + private Button hideHintButton; + private MenuItem actionHelpMenuItem; + private SwipeRefreshLayout swipeRefreshLayout; + private FloatingActionButton floatingActionButton; + private FragmentManager framan; + private Snackbar snackbar; + + /* + * Search mode + */ + private static final int SEARCH_BY_NAME = 0; + private static final int SEARCH_BY_ID = 1; + private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12 + private int searchMode; + private ImageButton addToFavorites; + + /* + * Options + */ + private final String OPTION_SHOW_LEGEND = "show_legend"; + private final String LOCATION_PERMISSION_GIVEN = "loc_permission"; + /* + * Status + */ + private DBStatusManager prefsManager; + private DBStatusManager.OnDBUpdateStatusChangeListener updatelistener; + private static final String DEBUG_TAG="BusTO - MainActivity"; + /* // useful for testing: + public class MockFetcher implements ArrivalsFetcher { + @Override + public Palina ReadArrivalTimesAll(String routeID, AtomicReference res) { + SystemClock.sleep(5000); + res.set(result.SERVER_ERROR); + return new Palina(); + } + } + private ArrivalsFetcher[] ArrivalFetchers = {new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher()};*/ + + private RecursionHelper ArrivalFetchersRecursionHelper = new RecursionHelper<>(new ArrivalsFetcher[] {new GTTJSONFetcher(), new FiveTScraperFetcher()}); + private RecursionHelper StopsFindersByNameRecursionHelper = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()}); + /* + * Position + */ + //Fine location criteria + private final Criteria cr = new Criteria(); + private boolean pendingNearbyStopsRequest = false; + private LocationManager locmgr; + /* + * Database Access + */ + private StopsDB stopsDB; + private UserDB userDB; + private FragmentHelper fh; + + ///////////////////////////////// EVENT HANDLERS /////////////////////////////////////////////// + + /* + * @see swipeRefreshLayout + */ + private Handler handler = new Handler(); + private final Runnable refreshing = new Runnable() { + public void run() { + if(framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment){ + ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame); + String stopName = fragment.getStopID(); + new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(stopName); + } else + new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); + } + }; + + //// MAIN METHOD /// + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + framan = getSupportFragmentManager(); + this.stopsDB = new StopsDB(getApplicationContext()); + this.userDB = new UserDB(getApplicationContext()); + setContentView(R.layout.activity_main); + busStopSearchByIDEditText = (EditText) findViewById(R.id.busStopSearchByIDEditText); + busStopSearchByNameEditText = (EditText) findViewById(R.id.busStopSearchByNameEditText); + progressBar = (ProgressBar) findViewById(R.id.progressBar); + howDoesItWorkTextView = (TextView) findViewById(R.id.howDoesItWorkTextView); + hideHintButton = (Button) findViewById(R.id.hideHintButton); + swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.listRefreshLayout); + floatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionButton); + + framan.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { + @Override + public void onBackStackChanged() { + Log.d("MainActivity, BusTO", "BACK STACK CHANGED"); + } + }); + + busStopSearchByIDEditText.setSelectAllOnFocus(true); + busStopSearchByIDEditText + .setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, + KeyEvent event) { + // IME_ACTION_SEARCH alphabetical option + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + onSearchClick(v); + return true; + } + return false; + } + }); + busStopSearchByNameEditText + .setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, + KeyEvent event) { + // IME_ACTION_SEARCH alphabetical option + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + onSearchClick(v); + return true; + } + return false; + } + }); + + // Called when the layout is pulled down + swipeRefreshLayout + .setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + handler.post(refreshing); + } + }); + + /** + * @author Marco Gagino!!! + */ + //swipeRefreshLayout.setColorSchemeColors(R.color.blue_500, R.color.orange_500); // setColorScheme is deprecated, setColorSchemeColors isn't + swipeRefreshLayout.setColorSchemeResources(R.color.blue_500,R.color.orange_500); + fh = new FragmentHelper(this,R.id.listRefreshLayout,R.id.resultFrame); + setSearchModeBusStopID(); + + //---------------------------- START INTENT CHECK QUEUE ------------------------------------ + + // Intercept calls from URL intent + boolean tryedFromIntent = false; + + String busStopID = null; + String busStopDisplayName = null; + Uri data = getIntent().getData(); + if (data != null) { + busStopID = getBusStopIDFromUri(data); + tryedFromIntent = true; + } + + // Intercept calls from other activities + if (!tryedFromIntent) { + Bundle b = getIntent().getExtras(); + if (b != null) { + busStopID = b.getString("bus-stop-ID"); + busStopDisplayName = b.getString("bus-stop-display-name"); + + /** + * I'm not very sure if you are coming from an Intent. + * Some launchers work in strange ways. + */ + tryedFromIntent = busStopID != null; + } + } + + //---------------------------- END INTENT CHECK QUEUE -------------------------------------- + + if (busStopID == null) { + // Show keyboard if can't start from intent + // JUST DON'T + // showKeyboard(); + + // You haven't obtained anything... from an intent? + if (tryedFromIntent) { + + // This shows a luser warning + ArrivalFetchersRecursionHelper.reset(); + Toast.makeText(getApplicationContext(), + R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show(); + } + } else { + // If you are here an intent has worked successfully + setBusStopSearchByIDEditText(busStopID); + /* + //THIS PART SHOULDN'T BE NECESSARY SINCE THE LAST SUCCESSFULLY SEARCHED BUS + // STOP IS ADDED AUTOMATICALLY + Stop nextStop = new Stop(busStopID); + // forcing it as user name even though it could be standard name, it doesn't really matter + nextStop.setStopUserName(busStopDisplayName); + //set stop as last succe + fh.setLastSuccessfullySearchedBusStop(nextStop); + */ + createFragmentForStop(busStopID); + } + //Try (hopefully) database update + //TODO: Start the service in foreground, check last time it ran before + DatabaseUpdateService.startDBUpdate(getApplicationContext()); + /* + Set database update + */ + updatelistener = new DBStatusManager.OnDBUpdateStatusChangeListener(){ + @Override + public boolean defaultStatusValue() { + return true; + } + + @Override + public void onDBStatusChanged(boolean updating) { + + if(updating){ + createDefaultSnackbar(); + } + else if(snackbar!=null){ + snackbar.dismiss(); + snackbar = null; + } + + + } + }; + prefsManager = new DBStatusManager(getApplicationContext(),updatelistener); + prefsManager.registerListener(); + + //locationHandler = new GPSLocationAdapter(getApplicationContext()); + //--------- NEARBY STOPS--------// + //SETUP LOCATION + locmgr = (LocationManager) getSystemService(LOCATION_SERVICE); + cr.setAccuracy(Criteria.ACCURACY_FINE); + cr.setAltitudeRequired(false); + cr.setBearingRequired(false); + cr.setCostAllowed(true); + cr.setPowerRequirement(Criteria.NO_REQUIREMENT); + //We want the nearby bus stops! + handler.post(new NearbyStopsRequester()); + //If there are no providers available, then, wait for them + + + Log.d("MainActivity", "Created"); + + } + + /** + * Reload bus stop timetable when it's fulled resumed from background. + */ + /** + @Override + protected void onPostResume() { + super.onPostResume(); + Log.d("ActivityMain", "onPostResume fired. Last successfully bus stop ID: " + fh.getLastSuccessfullySearchedBusStop()); + if (searchMode == SEARCH_BY_ID && fh.getLastSuccessfullySearchedBusStop() != null) { + setBusStopSearchByIDEditText(fh.getLastSuccessfullySearchedBusStop().ID); + //new asyncWgetBusStopFromBusStopID(lastSuccessfullySearchedBusStop.ID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); + new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(); + } else { + //we have new activity or we don't have a new searched stop. + //Let's search stops nearby + LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE); + Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.resultFrame); + + + } + //show the FAB since it remains hidden + floatingActionButton.show(); + + } + **/ + + + @Override + protected void onPause() { + super.onPause(); + fh.stopLastRequestIfNeeded(); + fh.setBlockAllActivities(true); + if(updatelistener!=null && prefsManager!=null) prefsManager.unregisterListener(); + locmgr.removeUpdates(locListener); + } + + @Override + protected void onResume() { + super.onResume(); + fh.setBlockAllActivities(false); + if(updatelistener!=null && prefsManager!=null) { + prefsManager.registerListener(); + if(prefsManager.isDBUpdating(true)){ + createDefaultSnackbar(); + } + } + if(pendingNearbyStopsRequest) + handler.post(new NearbyStopsRequester()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + + actionHelpMenuItem = menu.findItem(R.id.action_help); + return true; + } + + /** + * Callback fired when a MenuItem is selected + * + * @param item + * @return + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + switch (item.getItemId()) { + case android.R.id.home: + // Respond to the action bar's Up/Home button + NavUtils.navigateUpFromSameTask(this); + return true; + case R.id.action_help: + showHints(); + return true; + case R.id.action_favorites: + startActivity(new Intent(ActivityMain.this, ActivityFavorites.class)); + return true; + case R.id.action_about: + startActivity(new Intent(ActivityMain.this, ActivityAbout.class)); + return true; + case R.id.action_news: + openIceweasel("https://gitpull.it/w/librebusto/#how-to-get-news"); + return true; + case R.id.action_bugs: + openIceweasel("https://gitpull.it/w/librebusto/#how-to-create-a-bug-feature"); + return true; + case R.id.action_source: + openIceweasel("https://gitpull.it/w/librebusto/#how-to-hack-busto"); + return true; + case R.id.action_licence: + openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html"); + return true; + case R.id.action_settings: + Log.d("MAINBusTO","Pressed button preferences"); + startActivity(new Intent(ActivityMain.this,ActivitySettings.class)); + } + return super.onOptionsItemSelected(item); + } + + /** + * OK this is pure shit + * + * @param v View clicked + */ + public void onSearchClick(View v) { + if (searchMode == SEARCH_BY_ID) { + String busStopID = busStopSearchByIDEditText.getText().toString(); + //OLD ASYNCTASK + //new asyncWgetBusStopFromBusStopID(busStopID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop); + + if(busStopID == null || busStopID.length() <= 0) { + showMessage(R.string.insert_bus_stop_number_error); + toggleSpinner(false); + } else{ + new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(busStopID); + Log.d("MainActiv","Started search for arrivals of stop "+busStopID); + } + } else { // searchMode == SEARCH_BY_NAME + String query = busStopSearchByNameEditText.getText().toString(); + //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); + new AsyncDataDownload(AsyncDataDownload.RequestType.STOPS,fh).execute(query); + } + } + /** PERMISSION STUFF **/ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode){ + case PERMISSION_REQUEST_POSITION: + if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ + setOption(LOCATION_PERMISSION_GIVEN,true); + //if we sent a request for a new NearbyStopsFragment + if(pendingNearbyStopsRequest){ + pendingNearbyStopsRequest=false; + handler.post(new NearbyStopsRequester()); + } + + } else { + //permission denied + setOption(LOCATION_PERMISSION_GIVEN,false); + } + //add other cases for permissions + } + + } + + + @Override + public void createFragmentForStop(String ID) { + //new asyncWgetBusStopFromBusStopID(ID, ArrivalFetchersRecursionHelper,lastSuccessfullySearchedBusStop); + if(ID == null || ID.length() <= 0) { + // we're still in UI thread, no need to mess with Progress + showMessage(R.string.insert_bus_stop_number_error); + toggleSpinner(false); + } else { + new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(ID); + Log.d("MainActiv","Started search for arrivals of stop "+ID); + } + } + + + + /** + * QR scan button clicked + * + * @param v View QRButton clicked + */ + public void onQRButtonClick(View v) { + IntentIntegrator integrator = new IntentIntegrator(this); + integrator.initiateScan(); + } + + /** + * Receive the Barcode Scanner Intent + * + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + + Uri uri; + try { + uri = Uri.parse(scanResult != null ? scanResult.getContents() : null); // this apparently prevents NullPointerException. Somehow. + } catch (NullPointerException e) { + Toast.makeText(getApplicationContext(), + R.string.no_qrcode, Toast.LENGTH_SHORT).show(); + return; + } + + String busStopID = getBusStopIDFromUri(uri); + busStopSearchByIDEditText.setText(busStopID); + createFragmentForStop(busStopID); + } + + public void onHideHint(View v) { + hideHints(); + setOption(OPTION_SHOW_LEGEND, false); + } + + public void onToggleKeyboardLayout(View v) { + if (searchMode == SEARCH_BY_NAME) { + setSearchModeBusStopID(); + if (busStopSearchByIDEditText.requestFocus()) { + showKeyboard(); + } + } else { // searchMode == SEARCH_BY_ID + setSearchModeBusStopName(); + if (busStopSearchByNameEditText.requestFocus()) { + showKeyboard(); + } + } + } + private void createDefaultSnackbar(){ + if(snackbar==null){ + snackbar = Snackbar.make(findViewById(R.id.searchButton),R.string.database_update_message,Snackbar.LENGTH_INDEFINITE); + } + snackbar.show(); + } + ///////////////////////////////// POSITION STUFF////////////////////////////////////////////// + + private void resolveStopRequest(String provider){ + Log.d(DEBUG_TAG,"Provider "+provider+" got enabled"); + if(locmgr!=null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)){ + pendingNearbyStopsRequest = false; + handler.post(new NearbyStopsRequester()); + } + } + final LocationListener locListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + Log.d(DEBUG_TAG,"Location changed"); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + Log.d(DEBUG_TAG,"Location provider status: "+status); + if(status== LocationProvider.AVAILABLE){ + resolveStopRequest(provider); + } + } + + @Override + public void onProviderEnabled(String provider) { + resolveStopRequest(provider); + } + + @Override + public void onProviderDisabled(String provider) { + + } + }; + + class NearbyStopsRequester implements Runnable{ + @SuppressLint("MissingPermission") + @Override + public void run() { + final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false); + + if(!canRunPosition){ + pendingNearbyStopsRequest = true; + assertLocationPermissions(); + return; + } else setOption(LOCATION_PERMISSION_GIVEN,true); + + LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE); + if(locManager == null) { + Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment"); + return; + } + if(anyLocationProviderMatchesCriteria(locManager,cr,true) && fh.getLastSuccessfullySearchedBusStop()==null) { + //Go ahead with the request + Log.d("mainActivity","Recreating stop fragment"); + swipeRefreshLayout.setVisibility(View.VISIBLE); + NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS); + Fragment oldFrag = framan.findFragmentById(R.id.resultFrame); + FragmentTransaction ft = framan.beginTransaction(); + if(oldFrag!=null) + ft.remove(oldFrag); + ft.add(R.id.resultFrame,fragment,"nearbyStop_correct"); + ft.commit(); + framan.executePendingTransactions(); + pendingNearbyStopsRequest = false; + } else if(!anyLocationProviderMatchesCriteria(locManager,cr,true)){ + //Wait for the providers + Log.d(DEBUG_TAG,"Queuing position request"); + pendingNearbyStopsRequest = true; + locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,10,0.1f,locListener); + } + + } + } + + private boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled){ + List providers = mng.getProviders(cr,enabled); + Log.d(DEBUG_TAG,"Getting enabled location providers: "); + for(String s: providers){ + Log.d(DEBUG_TAG,"Provider "+s); + } + return providers.size()>0; + } + + + + ///////////////////////////////// OTHER STUFF ////////////////////////////////////////////////// + + /** + * Get the last successfully searched bus stop or NULL + * + * @return + */ + @Override + public Stop getLastSuccessfullySearchedBusStop() { + return fh.getLastSuccessfullySearchedBusStop(); + } + + /** + * Get the last successfully searched bus stop ID or NULL + * + * @return + */ + @Override + public String getLastSuccessfullySearchedBusStopID() { + Stop stop = getLastSuccessfullySearchedBusStop(); + return stop == null ? null : stop.ID; + } + + /** + * Update the star "Add to favorite" icon + */ + @Override + public void updateStarIconFromLastBusStop() { + + // no favorites no party! + addToFavorites = (ImageButton) findViewById(R.id.addToFavorites); + if (addToFavorites == null) { + Log.d("MainActivity", "Why the fuck the star is not here?!"); + return; + } + + // check if there is a last Stop + String stopID = getLastSuccessfullySearchedBusStopID(); + 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); + } + } + + /** + * Check if the last Bus Stop is in the favorites + * + * @return + */ + public boolean isStopInFavorites(String busStopId) { + boolean found = false; + + // no stop no party + if(busStopId != null) { + SQLiteDatabase userDB = new UserDB(getApplicationContext()).getReadableDatabase(); + found = UserDB.isStopInFavorites(userDB, busStopId); + } + + return found; + } + + /** + * Add the last Stop to favorites + */ + @Override + public void toggleLastStopToFavorites() { + Stop stop = getLastSuccessfullySearchedBusStop(); + if(stop != null) { + + // toggle the status in background + new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE) { + + /** + * Callback fired when the Stop is saved in the favorites + * @param result + */ + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + + // update the star icon + updateStarIconFromLastBusStop(); + } + + }.execute(stop); + } else { + // this case have no sense, but just immediately update the favorite icon + updateStarIconFromLastBusStop(); + } + } + + @Override + public void showFloatingActionButton(boolean yes) { + if(yes) floatingActionButton.show(); + else floatingActionButton.hide(); + } + + @Override + public void enableRefreshLayout(boolean yes) { + swipeRefreshLayout.setEnabled(yes); + } + + ////////////////////////////////////// GUI HELPERS ///////////////////////////////////////////// + @Override + public void showKeyboard() { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText; + imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + + @Override + public void showMessage(int messageID) { + Toast.makeText(getApplicationContext(), messageID, Toast.LENGTH_SHORT).show(); + } + + private void setSearchModeBusStopID() { + searchMode = SEARCH_BY_ID; + busStopSearchByNameEditText.setVisibility(View.GONE); + busStopSearchByNameEditText.setText(""); + busStopSearchByIDEditText.setVisibility(View.VISIBLE); + floatingActionButton.setImageResource(R.drawable.alphabetical); + } + + private void setSearchModeBusStopName() { + searchMode = SEARCH_BY_NAME; + busStopSearchByIDEditText.setVisibility(View.GONE); + busStopSearchByIDEditText.setText(""); + busStopSearchByNameEditText.setVisibility(View.VISIBLE); + floatingActionButton.setImageResource(R.drawable.numeric); + } + + /** + * Having that cursor at the left of the edit text makes me cancer. + * @param busStopID bus stop ID + */ + private void setBusStopSearchByIDEditText(String busStopID) { + busStopSearchByIDEditText.setText(busStopID); + busStopSearchByIDEditText.setSelection(busStopID.length()); + } + + private void showHints() { + howDoesItWorkTextView.setVisibility(View.VISIBLE); + hideHintButton.setVisibility(View.VISIBLE); + actionHelpMenuItem.setVisible(false); + } + + private void hideHints() { + howDoesItWorkTextView.setVisibility(View.GONE); + hideHintButton.setVisibility(View.GONE); + actionHelpMenuItem.setVisible(true); + } + + //TODO: toggle spinner from mainActivity + @Override + public void toggleSpinner(boolean enable) { + if (enable) { + //already set by the RefreshListener when needed + //swipeRefreshLayout.setRefreshing(true); + progressBar.setVisibility(View.VISIBLE); + } else { + swipeRefreshLayout.setRefreshing(false); + progressBar.setVisibility(View.GONE); + } + } + + private void prepareGUIForBusLines() { + swipeRefreshLayout.setEnabled(true); + swipeRefreshLayout.setVisibility(View.VISIBLE); + actionHelpMenuItem.setVisible(true); + } + + private void prepareGUIForBusStops() { + swipeRefreshLayout.setEnabled(false); + swipeRefreshLayout.setVisibility(View.VISIBLE); + actionHelpMenuItem.setVisible(false); + } + + + /** + * This provides a temporary fix to make the transition + * to a single asynctask go smoother + * @param fragmentType the type of fragment created + */ + @Override + public void readyGUIfor(FragmentKind fragmentType) { + hideKeyboard(); + //if we are getting results, already, stop waiting for nearbyStops + if(pendingNearbyStopsRequest && (fragmentType==FragmentKind.ARRIVALS || fragmentType==FragmentKind.STOPS)) { + locmgr.removeUpdates(locListener); + pendingNearbyStopsRequest = false; + } + if(fragmentType==null) Log.e("ActivityMain","Problem with fragmentType"); + else + switch (fragmentType){ + case ARRIVALS: + prepareGUIForBusLines(); + if (getOption(OPTION_SHOW_LEGEND, true)) { + showHints(); + } + break; + case STOPS: + prepareGUIForBusStops(); + break; + default: + Log.e("BusTO Activity","Called readyGUI with unsupported type of Fragment"); + return; + } + // Shows hints + + } + + /** + * Open an URL in the default browser. + * + * @param url URL + */ + public void openIceweasel(String url) { + Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(browserIntent1); + } + + ///////////////////// INTENT HELPER //////////////////////////////////////////////////////////// + + /** + * Try to extract the bus stop ID from a URi + * + * @param uri The URL + * @return bus stop ID or null + */ + public static String getBusStopIDFromUri(Uri uri) { + String busStopID; + + // everithing catches fire when passing null to a switch. + String host = uri.getHost(); + if(host == null) { + Log.e("ActivityMain", "Not an URL: " + uri); + return null; + } + + switch(host) { + case "m.gtt.to.it": + // http://m.gtt.to.it/m/it/arrivi.jsp?n=1254 + busStopID = uri.getQueryParameter("n"); + if (busStopID == null) { + Log.e("ActivityMain", "Expected ?n from: " + uri); + } + break; + case "www.gtt.to.it": + case "gtt.to.it": + // http://www.gtt.to.it/cms/percorari/arrivi?palina=1254 + busStopID = uri.getQueryParameter("palina"); + if (busStopID == null) { + Log.e("ActivityMain", "Expected ?palina from: " + uri); + } + break; + default: + Log.e("ActivityMain", "Unexpected intent URL: " + uri); + busStopID = null; + } + return busStopID; + } + + public void changeStarType(String stopID) { + if(isStopInFavorites(stopID)) { + changeStarFilled(); + } else { + changeStarOutline(); + } + } + + public void changeStarFilled() { + addToFavorites.setImageResource(R.drawable.ic_star_filled); + } + + public void changeStarOutline() { + addToFavorites.setImageResource(R.drawable.ic_star_outline); + } + + + } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java --- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java +++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java @@ -1,212 +1,215 @@ -/* - BusTO - Fragments components - Copyright (C) 2018 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.fragments; - - -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.util.Log; -import android.widget.TextView; - -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.backend.DBStatusManager; -import it.reyboz.bustorino.middleware.AppDataProvider; -import it.reyboz.bustorino.middleware.NextGenDB; -import it.reyboz.bustorino.middleware.UserDB; - -public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks { - - private final static String KEY_STOP_ID = "stopid"; - private final static String KEY_STOP_NAME = "stopname"; - private final static String DEBUG_TAG = "BUSTOArrivalsFragment"; - private final static int loaderFavId = 2; - private final static int loaderStopId = 1; - private @Nullable String stopID,stopName; - private TextView messageTextView; - private DBStatusManager prefs; - private DBStatusManager.OnDBUpdateStatusChangeListener listener; - private boolean justCreated = false; - - public static ArrivalsFragment newInstance(String stopID){ - Bundle args = new Bundle(); - args.putString(KEY_STOP_ID,stopID); - ArrivalsFragment fragment = new ArrivalsFragment(); - //parameter for ResultListFragment - args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS); - fragment.setArguments(args); - return fragment; - } - public static ArrivalsFragment newInstance(String stopID,String stopName){ - ArrivalsFragment fragment = newInstance(stopID); - Bundle args = fragment.getArguments(); - args.putString(KEY_STOP_NAME,stopName); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - stopID = getArguments().getString(KEY_STOP_ID); - //this might really be null - stopName = getArguments().getString(KEY_STOP_NAME); - final ArrivalsFragment f = this; - listener = new DBStatusManager.OnDBUpdateStatusChangeListener() { - @Override - public void onDBStatusChanged(boolean updating) { - if(!updating){ - getLoaderManager().restartLoader(loaderFavId,getArguments(),f); - } else { - final LoaderManager lm = getLoaderManager(); - lm.destroyLoader(loaderFavId); - lm.destroyLoader(loaderStopId); - } - } - - @Override - public boolean defaultStatusValue() { - return true; - } - }; - prefs = new DBStatusManager(getContext().getApplicationContext(),listener); - justCreated = true; - - } - - @Override - public void onResume() { - super.onResume(); - LoaderManager loaderManager = getLoaderManager(); - - if(stopID!=null){ - //refresh the arrivals - if(!justCreated) - mListener.createFragmentForStop(stopID); - else justCreated = false; - //start the loader - if(prefs.isDBUpdating(true)){ - prefs.registerListener(); - } else { - loaderManager.restartLoader(loaderFavId, getArguments(), this); - } - updateMessage(); - } - } - - @Nullable - public String getStopID() { - return stopID; - } - - /** - * Update the message in the fragment - * - * It may eventually change the "Add to Favorite" icon - */ - private void updateMessage(){ - String message = null; - if (stopName != null && stopID != null && stopName.length() > 0) { - message = (stopID.concat(" - ").concat(stopName)); - } else if(stopID!=null) { - message = stopID; - } else { - Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong"); - } - if(message!=null) { - setTextViewMessage(getString(R.string.passages,message)); - } - - // whatever is the case, update the star icon - mListener.updateStarIconFromLastBusStop(); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - if(args.getString(KEY_STOP_ID)==null) return null; - final String stopID = args.getString(KEY_STOP_ID); - final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete(); - CursorLoader cl; - switch (id){ - case loaderFavId: - builder.appendPath("favorites").appendPath(stopID); - cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null); - - break; - case loaderStopId: - builder.appendPath("stop").appendPath(stopID); - cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME}, - null,null,null); - break; - default: - return null; - } - cl.setUpdateThrottle(500); - return cl; - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - - switch (loader.getId()){ - case loaderFavId: - final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]); - if(data.getCount()>0){ - data.moveToFirst(); - final String probableName = data.getString(colUserName); - if(probableName!=null && !probableName.isEmpty()){ - stopName = probableName; - updateMessage(); - } - } - if(stopName == null){ - //stop is not inside the favorites and wasn't provided - Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB"); - getLoaderManager().restartLoader(loaderStopId,getArguments(),this); - } - break; - case loaderStopId: - if(data.getCount()>0){ - data.moveToFirst(); - stopName = data.getString(data.getColumnIndex( - NextGenDB.Contract.StopsTable.COL_NAME - )); - updateMessage(); - } else { - Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL"); - } - } - - } - - @Override - public void onPause() { - if(listener!=null) - prefs.unregisterListener(); - super.onPause(); - } - - @Override - public void onLoaderReset(Loader loader) { - //NOTHING TO DO - } -} +/* + BusTO - Fragments components + Copyright (C) 2018 Fabio Mazza + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino.fragments; + + +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.Log; +import android.widget.ImageButton; +import android.widget.TextView; + +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.DBStatusManager; +import it.reyboz.bustorino.middleware.AppDataProvider; +import it.reyboz.bustorino.middleware.NextGenDB; +import it.reyboz.bustorino.middleware.UserDB; + +public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks { + + private final static String KEY_STOP_ID = "stopid"; + private final static String KEY_STOP_NAME = "stopname"; + private final static String DEBUG_TAG = "BUSTOArrivalsFragment"; + private final static int loaderFavId = 2; + private final static int loaderStopId = 1; + private @Nullable String stopID,stopName; + private TextView messageTextView; + private DBStatusManager prefs; + private DBStatusManager.OnDBUpdateStatusChangeListener listener; + private boolean justCreated = false; + private ImageButton addToFavorites; + + + public static ArrivalsFragment newInstance(String stopID){ + Bundle args = new Bundle(); + args.putString(KEY_STOP_ID,stopID); + ArrivalsFragment fragment = new ArrivalsFragment(); + //parameter for ResultListFragment + args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS); + fragment.setArguments(args); + return fragment; + } + public static ArrivalsFragment newInstance(String stopID,String stopName){ + ArrivalsFragment fragment = newInstance(stopID); + Bundle args = fragment.getArguments(); + args.putString(KEY_STOP_NAME,stopName); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + stopID = getArguments().getString(KEY_STOP_ID); + //this might really be null + stopName = getArguments().getString(KEY_STOP_NAME); + final ArrivalsFragment f = this; + listener = new DBStatusManager.OnDBUpdateStatusChangeListener() { + @Override + public void onDBStatusChanged(boolean updating) { + if(!updating){ + getLoaderManager().restartLoader(loaderFavId,getArguments(),f); + } else { + final LoaderManager lm = getLoaderManager(); + lm.destroyLoader(loaderFavId); + lm.destroyLoader(loaderStopId); + } + } + + @Override + public boolean defaultStatusValue() { + return true; + } + }; + prefs = new DBStatusManager(getContext().getApplicationContext(),listener); + justCreated = true; + + } + + @Override + public void onResume() { + super.onResume(); + LoaderManager loaderManager = getLoaderManager(); + + if(stopID!=null){ + //refresh the arrivals + if(!justCreated) + mListener.createFragmentForStop(stopID); + else justCreated = false; + //start the loader + if(prefs.isDBUpdating(true)){ + prefs.registerListener(); + } else { + loaderManager.restartLoader(loaderFavId, getArguments(), this); + } + updateMessage(); + } + } + + @Nullable + public String getStopID() { + return stopID; + } + + /** + * Update the message in the fragment + * + * It may eventually change the "Add to Favorite" icon + */ + private void updateMessage(){ + String message = null; + if (stopName != null && stopID != null && stopName.length() > 0) { + message = (stopID.concat(" - ").concat(stopName)); + } else if(stopID!=null) { + message = stopID; + } else { + Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong"); + } + if(message!=null) { + setTextViewMessage(getString(R.string.passages,message)); + } + + // whatever is the case, update the star icon + mListener.updateStarIconFromLastBusStop(); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + if(args.getString(KEY_STOP_ID)==null) return null; + final String stopID = args.getString(KEY_STOP_ID); + final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete(); + CursorLoader cl; + switch (id){ + case loaderFavId: + builder.appendPath("favorites").appendPath(stopID); + cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null); + + break; + case loaderStopId: + builder.appendPath("stop").appendPath(stopID); + cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME}, + null,null,null); + break; + default: + return null; + } + cl.setUpdateThrottle(500); + return cl; + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + + switch (loader.getId()){ + case loaderFavId: + final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]); + if(data.getCount()>0){ + data.moveToFirst(); + final String probableName = data.getString(colUserName); + if(probableName!=null && !probableName.isEmpty()){ + stopName = probableName; + updateMessage(); + } + } + if(stopName == null){ + //stop is not inside the favorites and wasn't provided + Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB"); + getLoaderManager().restartLoader(loaderStopId,getArguments(),this); + } + break; + case loaderStopId: + if(data.getCount()>0){ + data.moveToFirst(); + stopName = data.getString(data.getColumnIndex( + NextGenDB.Contract.StopsTable.COL_NAME + )); + updateMessage(); + } else { + Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL"); + } + } + + } + + @Override + public void onPause() { + if(listener!=null) + prefs.unregisterListener(); + super.onPause(); + } + + @Override + public void onLoaderReset(Loader loader) { + //NOTHING TO DO + } +} diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java --- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -1,230 +1,230 @@ -/* - BusTO (fragments) - Copyright (C) 2018 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.fragments; - - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.sqlite.SQLiteException; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.widget.SwipeRefreshLayout; -import android.util.Log; -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.adapters.PalinaAdapter; -import it.reyboz.bustorino.backend.Fetcher; -import it.reyboz.bustorino.backend.Palina; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.middleware.*; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** - * Helper class to manage the fragments and their needs - */ -public class FragmentHelper { - GeneralActivity act; - private Stop lastSuccessfullySearchedBusStop; - //support for multiple frames - private int primaryFrameLayout,secondaryFrameLayout, swipeRefID; - public static final int NO_FRAME = -3; - private WeakReference lastTaskRef; - private NextGenDB newDBHelper; - private boolean shouldHaltAllActivities=false; - - - public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) { - this(act,swipeRefID,mainFrame,NO_FRAME); - } - - public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) { - this.act = act; - this.swipeRefID = swipeRefID; - this.primaryFrameLayout = primaryFrameLayout; - this.secondaryFrameLayout = secondaryFrameLayout; - newDBHelper = NextGenDB.getInstance(act.getApplicationContext()); - } - - /** - * Get the last successfully searched bus stop or NULL - * - * @return - */ - public Stop getLastSuccessfullySearchedBusStop() { - return lastSuccessfullySearchedBusStop; - } - - public void setLastSuccessfullySearchedBusStop(Stop stop) { - this.lastSuccessfullySearchedBusStop = stop; - } - - public void setLastTaskRef(WeakReference lastTaskRef) { - this.lastTaskRef = lastTaskRef; - } - - /** - * Called when you need to create a fragment for a specified Palina - * @param p the Stop that needs to be displayed - */ - public void createOrUpdateStopFragment(Palina p){ - boolean sameFragment; - ArrivalsFragment arrivalsFragment; - - if(act==null || shouldHaltAllActivities) { - //SOMETHING WENT VERY WRONG - return; - } - - SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID); - FragmentManager fm = act.getSupportFragmentManager(); - - if(fm.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) { - arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame); - sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); - } else - sameFragment = false; - - setLastSuccessfullySearchedBusStop(p); - - if(!sameFragment) { - //set the String to be displayed on the fragment - String displayName = p.getStopDisplayName(); - String displayStuff; - - if (displayName != null && displayName.length() > 0) { - arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName); - } else { - arrivalsFragment = ArrivalsFragment.newInstance(p.ID); - } - attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p)); - } else { - Log.d("BusTO", "Same bus stop, accessing existing fragment"); - arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame); - } - - arrivalsFragment.setListAdapter(new PalinaAdapter(act.getApplicationContext(),p)); - act.hideKeyboard(); - toggleSpinner(false); - } - - /** - * Called when you need to display the results of a search of stops - * @param resultList the List of stops found - * @param query String queried - */ - public void createFragmentFor(List resultList,String query){ - act.hideKeyboard(); - StopListFragment listfragment = StopListFragment.newInstance(query); - attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query); - listfragment.setStopList(resultList); - toggleSpinner(false); - - } - - /** - * Wrapper for toggleSpinner in Activity - * @param on new status of spinner system - */ - public void toggleSpinner(boolean on){ - if (act instanceof FragmentListener) - ((FragmentListener) act).toggleSpinner(on); - else { - SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID); - srl.setRefreshing(false); - } - } - - /** - * Attach a new fragment to a cointainer - * @param fm the FragmentManager - * @param fragment the Fragment - * @param sendToSecondaryFrame needs to be displayed in secondary frame or not - * @param tag tag for the fragment - */ - public void attachFragmentToContainer(FragmentManager fm,Fragment fragment, boolean sendToSecondaryFrame, String tag){ - FragmentTransaction ft = fm.beginTransaction(); - if(sendToSecondaryFrame && secondaryFrameLayout!=NO_FRAME) - ft.replace(secondaryFrameLayout,fragment,tag); - else ft.replace(primaryFrameLayout,fragment,tag); - ft.addToBackStack("state_"+tag); - ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); - ft.commit(); - //fm.executePendingTransactions(); - } - - synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){ - if(newDBHelper !=null) - try { - return newDBHelper.insertBatchContent(valuesArr, tableName); - } catch (SQLiteException exc){ - Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace()); - return -2; - } - else return -1; - } - - synchronized public ContentResolver getContentResolver(){ - return act.getContentResolver(); - } - - public void setBlockAllActivities(boolean shouldI) { - this.shouldHaltAllActivities = shouldI; - } - - public void stopLastRequestIfNeeded(){ - if(lastTaskRef == null) return; - AsyncDataDownload task = lastTaskRef.get(); - if(task!=null){ - task.cancel(true); - } - } - - /** - * Wrapper to show the errors/status that happened - * @param res result from Fetcher - */ - public void showErrorMessage(Fetcher.result res){ - //TODO: implement a common set of errors for all fragments - switch (res){ - case OK: - break; - case CLIENT_OFFLINE: - act.showMessage(R.string.network_error); - break; - case SERVER_ERROR: - if (act.isConnected()) { - act.showMessage(R.string.parsing_error); - } else { - act.showMessage(R.string.network_error); - } - case PARSER_ERROR: - default: - act.showMessage(R.string.internal_error); - break; - case QUERY_TOO_SHORT: - act.showMessage(R.string.query_too_short); - break; - case EMPTY_RESULT_SET: - act.showMessage(R.string.no_bus_stop_have_this_name); - break; - } - } - -} +/* + BusTO (fragments) + Copyright (C) 2018 Fabio Mazza + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino.fragments; + + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.sqlite.SQLiteException; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.adapters.PalinaAdapter; +import it.reyboz.bustorino.backend.Fetcher; +import it.reyboz.bustorino.backend.Palina; +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.middleware.*; + +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * Helper class to manage the fragments and their needs + */ +public class FragmentHelper { + GeneralActivity act; + private Stop lastSuccessfullySearchedBusStop; + //support for multiple frames + private int primaryFrameLayout,secondaryFrameLayout, swipeRefID; + public static final int NO_FRAME = -3; + private WeakReference lastTaskRef; + private NextGenDB newDBHelper; + private boolean shouldHaltAllActivities=false; + + + public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) { + this(act,swipeRefID,mainFrame,NO_FRAME); + } + + public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) { + this.act = act; + this.swipeRefID = swipeRefID; + this.primaryFrameLayout = primaryFrameLayout; + this.secondaryFrameLayout = secondaryFrameLayout; + newDBHelper = NextGenDB.getInstance(act.getApplicationContext()); + } + + /** + * Get the last successfully searched bus stop or NULL + * + * @return + */ + public Stop getLastSuccessfullySearchedBusStop() { + return lastSuccessfullySearchedBusStop; + } + + public void setLastSuccessfullySearchedBusStop(Stop stop) { + this.lastSuccessfullySearchedBusStop = stop; + } + + public void setLastTaskRef(WeakReference lastTaskRef) { + this.lastTaskRef = lastTaskRef; + } + + /** + * Called when you need to create a fragment for a specified Palina + * @param p the Stop that needs to be displayed + */ + public void createOrUpdateStopFragment(Palina p){ + boolean sameFragment; + ArrivalsFragment arrivalsFragment; + + if(act==null || shouldHaltAllActivities) { + //SOMETHING WENT VERY WRONG + return; + } + + SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID); + FragmentManager fm = act.getSupportFragmentManager(); + + if(fm.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) { + arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame); + sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); + } else + sameFragment = false; + + setLastSuccessfullySearchedBusStop(p); + + if(!sameFragment) { + //set the String to be displayed on the fragment + String displayName = p.getStopDisplayName(); + String displayStuff; + + if (displayName != null && displayName.length() > 0) { + arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName); + } else { + arrivalsFragment = ArrivalsFragment.newInstance(p.ID); + } + attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p)); + } else { + Log.d("BusTO", "Same bus stop, accessing existing fragment"); + arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame); + } + + arrivalsFragment.setListAdapter(new PalinaAdapter(act.getApplicationContext(),p)); + act.hideKeyboard(); + toggleSpinner(false); + } + + /** + * Called when you need to display the results of a search of stops + * @param resultList the List of stops found + * @param query String queried + */ + public void createFragmentFor(List resultList,String query){ + act.hideKeyboard(); + StopListFragment listfragment = StopListFragment.newInstance(query); + attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query); + listfragment.setStopList(resultList); + toggleSpinner(false); + + } + + /** + * Wrapper for toggleSpinner in Activity + * @param on new status of spinner system + */ + public void toggleSpinner(boolean on){ + if (act instanceof FragmentListener) + ((FragmentListener) act).toggleSpinner(on); + else { + SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID); + srl.setRefreshing(false); + } + } + + /** + * Attach a new fragment to a cointainer + * @param fm the FragmentManager + * @param fragment the Fragment + * @param sendToSecondaryFrame needs to be displayed in secondary frame or not + * @param tag tag for the fragment + */ + public void attachFragmentToContainer(FragmentManager fm,Fragment fragment, boolean sendToSecondaryFrame, String tag){ + FragmentTransaction ft = fm.beginTransaction(); + if(sendToSecondaryFrame && secondaryFrameLayout!=NO_FRAME) + ft.replace(secondaryFrameLayout,fragment,tag); + else ft.replace(primaryFrameLayout,fragment,tag); + ft.addToBackStack("state_"+tag); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); + ft.commit(); + //fm.executePendingTransactions(); + } + + synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){ + if(newDBHelper !=null) + try { + return newDBHelper.insertBatchContent(valuesArr, tableName); + } catch (SQLiteException exc){ + Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace()); + return -2; + } + else return -1; + } + + synchronized public ContentResolver getContentResolver(){ + return act.getContentResolver(); + } + + public void setBlockAllActivities(boolean shouldI) { + this.shouldHaltAllActivities = shouldI; + } + + public void stopLastRequestIfNeeded(){ + if(lastTaskRef == null) return; + AsyncDataDownload task = lastTaskRef.get(); + if(task!=null){ + task.cancel(true); + } + } + + /** + * Wrapper to show the errors/status that happened + * @param res result from Fetcher + */ + public void showErrorMessage(Fetcher.result res){ + //TODO: implement a common set of errors for all fragments + switch (res){ + case OK: + break; + case CLIENT_OFFLINE: + act.showMessage(R.string.network_error); + break; + case SERVER_ERROR: + if (act.isConnected()) { + act.showMessage(R.string.parsing_error); + } else { + act.showMessage(R.string.network_error); + } + case PARSER_ERROR: + default: + act.showMessage(R.string.internal_error); + break; + case QUERY_TOO_SHORT: + act.showMessage(R.string.query_too_short); + break; + case EMPTY_RESULT_SET: + act.showMessage(R.string.no_bus_stop_have_this_name); + break; + } + } + +} diff --git a/src/it/reyboz/bustorino/fragments/FragmentListener.java b/src/it/reyboz/bustorino/fragments/FragmentListener.java --- a/src/it/reyboz/bustorino/fragments/FragmentListener.java +++ b/src/it/reyboz/bustorino/fragments/FragmentListener.java @@ -1,72 +1,72 @@ -/* - BusTO - Fragments components - Copyright (C) 2018 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.fragments; - -import it.reyboz.bustorino.backend.Stop; - -public interface FragmentListener { - void toggleSpinner(boolean state); - /** - * Sends the message to the activity to adapt the GUI - * to the fragment that has been attached - * @param fragmentType the type of fragment attached - */ - void readyGUIfor(FragmentKind fragmentType); - /** - * Houston, we need another fragment! - * - * @param ID the Stop ID - */ - void createFragmentForStop(String ID); - - /** - * Add the last successfully searched stop to the favorites - */ - void toggleLastStopToFavorites(); - - /** - * Get the last successfully searched bus stop or NULL - * - * @return - */ - Stop getLastSuccessfullySearchedBusStop(); - - /** - * Get the last successfully searched bus stop ID or NULL - * - * @return - */ - String getLastSuccessfullySearchedBusStopID(); - - /** - * Automatically update the "Add to favorite" star icon - */ - void updateStarIconFromLastBusStop(); - - /** - * Tell the activity that we need to disable/enable its floatingActionButton - * @param yes or no - */ - void showFloatingActionButton(boolean yes); - - /** - * Tell activity that we need to enable/disable the refreshLayout - * @param yes or no - */ - void enableRefreshLayout(boolean yes); -} +/* + BusTO - Fragments components + Copyright (C) 2018 Fabio Mazza + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package it.reyboz.bustorino.fragments; + +import it.reyboz.bustorino.backend.Stop; + +public interface FragmentListener { + void toggleSpinner(boolean state); + /** + * Sends the message to the activity to adapt the GUI + * to the fragment that has been attached + * @param fragmentType the type of fragment attached + */ + void readyGUIfor(FragmentKind fragmentType); + /** + * Houston, we need another fragment! + * + * @param ID the Stop ID + */ + void createFragmentForStop(String ID); + + /** + * Add the last successfully searched stop to the favorites + */ + void toggleLastStopToFavorites(); + + /** + * Get the last successfully searched bus stop or NULL + * + * @return + */ + Stop getLastSuccessfullySearchedBusStop(); + + /** + * Get the last successfully searched bus stop ID or NULL + * + * @return + */ + String getLastSuccessfullySearchedBusStopID(); + + /** + * Automatically update the "Add to favorite" star icon + */ + void updateStarIconFromLastBusStop(); + + /** + * Tell the activity that we need to disable/enable its floatingActionButton + * @param yes or no + */ + void showFloatingActionButton(boolean yes); + + /** + * Tell activity that we need to enable/disable the refreshLayout + * @param yes or no + */ + void enableRefreshLayout(boolean yes); +} diff --git a/src/it/reyboz/bustorino/fragments/ResultListFragment.java b/src/it/reyboz/bustorino/fragments/ResultListFragment.java --- a/src/it/reyboz/bustorino/fragments/ResultListFragment.java +++ b/src/it/reyboz/bustorino/fragments/ResultListFragment.java @@ -1,294 +1,314 @@ -/* - BusTO - Fragments 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 . - */ - - -package it.reyboz.bustorino.fragments; - -import android.content.Context; -import android.os.Bundle; -import android.os.Parcelable; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import android.widget.*; -import android.support.design.widget.FloatingActionButton; - -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.backend.FiveTNormalizer; -import it.reyboz.bustorino.backend.Palina; -import it.reyboz.bustorino.backend.Route; -import it.reyboz.bustorino.backend.Stop; - -/** - * This is a generalized fragment that can be used both for - * - * - */ -public class ResultListFragment extends Fragment{ - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - static final String LIST_TYPE = "list-type"; - private static final String STOP_TITLE = "messageExtra"; - protected static final String LIST_STATE = "list_state"; - - - private static final String MESSAGE_TEXT_VIEW = "message_text_view"; - private FragmentKind adapterKind; - - private boolean adapterSet = false; - protected FragmentListener mListener; - private TextView messageTextView; - - private FloatingActionButton fabutton; - private ListView resultsListView; - private ListAdapter mListAdapter = null; - boolean listShown; - private Parcelable mListInstanceState = null; - - public ResultListFragment() { - // Required empty public constructor - } - - public ListView getResultsListView() { - return resultsListView; - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param listType whether the list is used for STOPS or LINES (Orari) - * @return A new instance of fragment ResultListFragment. - */ - public static ResultListFragment newInstance(FragmentKind listType, String eventualStopTitle) { - ResultListFragment fragment = new ResultListFragment(); - Bundle args = new Bundle(); - args.putSerializable(LIST_TYPE, listType); - if (eventualStopTitle != null) { - args.putString(STOP_TITLE, eventualStopTitle); - } - fragment.setArguments(args); - return fragment; - } - - public static ResultListFragment newInstance(FragmentKind listType) { - return newInstance(listType, null); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - adapterKind = (FragmentKind) getArguments().getSerializable(LIST_TYPE); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.fragment_list_view, container, false); - messageTextView = (TextView) root.findViewById(R.id.messageTextView); - - if (adapterKind != null) { - resultsListView = (ListView) root.findViewById(R.id.resultsListView); - switch (adapterKind) { - case STOPS: - resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - /* - * Casting because of Javamerda - * @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener - */ - Stop busStop = (Stop) parent.getItemAtPosition(position); - mListener.createFragmentForStop(busStop.ID); - } - }); - - //set the textviewMessage - setTextViewMessage(getString(R.string.results)); - break; - case ARRIVALS: - resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - String routeName; - - Route r = (Route) parent.getItemAtPosition(position); - routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay()); - if (routeName == null) { - routeName = r.getNameForDisplay(); - } - if (r.destinazione == null || r.destinazione.length() == 0) { - Toast.makeText(getContext(), - getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(getContext(), - getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show(); - } - } - }); - String displayName = getArguments().getString(STOP_TITLE); - setTextViewMessage(String.format( - getString(R.string.passages), displayName)); - break; - default: - throw new IllegalStateException("Argument passed was not of a supported type"); - } - - String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW); - if (probablemessage != null) { - //Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage); - messageTextView.setText(probablemessage); - messageTextView.setVisibility(View.VISIBLE); - } - - } else - Log.d(getString(R.string.list_fragment_debug), "No content root for fragment"); - return root; - } - - public boolean isFragmentForTheSameStop(Palina p) { - return adapterKind.equals(FragmentKind.ARRIVALS) && getTag().equals(getFragmentTag(p)); - } - - public static String getFragmentTag(Palina p) { - return p.ID; - } - - - @Override - public void onResume() { - super.onResume(); - //Log.d(getString(R.string.list_fragment_debug),"Fragment restored, saved listAdapter is "+(mListAdapter)); - if (mListAdapter != null) { - - ListAdapter adapter = mListAdapter; - mListAdapter = null; - setListAdapter(adapter); - } - if (mListInstanceState != null) { - Log.d("resultsListView", "trying to restore instance state"); - resultsListView.onRestoreInstanceState(mListInstanceState); - } - switch (adapterKind) { - case ARRIVALS: - resultsListView.setOnScrollListener(new CommonScrollListener(mListener, true)); - fabutton.show(); - break; - case STOPS: - resultsListView.setOnScrollListener(new CommonScrollListener(mListener, false)); - break; - default: - //NONE - } - mListener.readyGUIfor(adapterKind); - - } - - @Override - public void onPause() { - if (adapterKind.equals(FragmentKind.ARRIVALS)) { - SwipeRefreshLayout reflay = (SwipeRefreshLayout) getActivity().findViewById(R.id.listRefreshLayout); - reflay.setEnabled(false); - Log.d("BusTO Fragment " + this.getTag(), "RefreshLayout disabled"); - } - super.onPause(); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof FragmentListener) { - mListener = (FragmentListener) context; - fabutton = (FloatingActionButton) getActivity().findViewById(R.id.floatingActionButton); - } else { - throw new RuntimeException(context.toString() - + " must implement ResultFragmentListener"); - } - - } - - - @Override - public void onDetach() { - mListener = null; - if (fabutton != null) - fabutton.show(); - super.onDetach(); - } - - - @Override - public void onDestroyView() { - resultsListView = null; - //Log.d(getString(R.string.list_fragment_debug), "called onDestroyView"); - getArguments().putString(MESSAGE_TEXT_VIEW, messageTextView.getText().toString()); - super.onDestroyView(); - } - - - @Override - public void onViewStateRestored(@Nullable Bundle savedInstanceState) { - super.onViewStateRestored(savedInstanceState); - Log.d("ResultListFragment", "onViewStateRestored"); - if (savedInstanceState != null) { - mListInstanceState = savedInstanceState.getParcelable(LIST_STATE); - Log.d("ResultListFragment", "listInstanceStatePresent :" + mListInstanceState); - } - } - - public void setListAdapter(ListAdapter adapter) { - boolean hadAdapter = mListAdapter != null; - mListAdapter = adapter; - if (resultsListView != null) { - resultsListView.setAdapter(adapter); - resultsListView.setVisibility(View.VISIBLE); - } - } - - /** - * Set the message textView - * @param message the whole message to write in the textView - */ - public void setTextViewMessage(String message) { - messageTextView.setText(message); - switch (adapterKind) { - case ARRIVALS: - messageTextView.setClickable(true); - messageTextView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // add/remove the stop in the favorites - mListener.toggleLastStopToFavorites(); - } - }); - break; - case STOPS: - messageTextView.setClickable(false); - break; - } - - messageTextView.setVisibility(View.VISIBLE); - } -} +/* + BusTO - Fragments 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 . + */ + + +package it.reyboz.bustorino.fragments; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import android.widget.*; +import android.support.design.widget.FloatingActionButton; + +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.FiveTNormalizer; +import it.reyboz.bustorino.backend.Palina; +import it.reyboz.bustorino.backend.Route; +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.middleware.UserDB; + +/** + * This is a generalized fragment that can be used both for + * + * + */ +public class ResultListFragment extends Fragment{ + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + static final String LIST_TYPE = "list-type"; + private static final String STOP_TITLE = "messageExtra"; + protected static final String LIST_STATE = "list_state"; + + + private static final String MESSAGE_TEXT_VIEW = "message_text_view"; + private FragmentKind adapterKind; + + private boolean adapterSet = false; + protected FragmentListener mListener; + private TextView messageTextView; + private ImageButton addToFavorites; + + private FloatingActionButton fabutton; + private ListView resultsListView; + private ListAdapter mListAdapter = null; + boolean listShown; + private Parcelable mListInstanceState = null; + + public ResultListFragment() { + // Required empty public constructor + } + + public ListView getResultsListView() { + return resultsListView; + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param listType whether the list is used for STOPS or LINES (Orari) + * @return A new instance of fragment ResultListFragment. + */ + public static ResultListFragment newInstance(FragmentKind listType, String eventualStopTitle) { + ResultListFragment fragment = new ResultListFragment(); + Bundle args = new Bundle(); + args.putSerializable(LIST_TYPE, listType); + if (eventualStopTitle != null) { + args.putString(STOP_TITLE, eventualStopTitle); + } + fragment.setArguments(args); + return fragment; + } + + public static ResultListFragment newInstance(FragmentKind listType) { + return newInstance(listType, null); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + adapterKind = (FragmentKind) getArguments().getSerializable(LIST_TYPE); + } + } + + /** + * Check if the last Bus Stop is in the favorites + * @return + */ + public boolean isStopInFavorites(String busStopId) { + boolean found = false; + + // no stop no party + if(busStopId != null) { + SQLiteDatabase userDB = new UserDB(getContext()).getReadableDatabase(); + found = UserDB.isStopInFavorites(userDB, busStopId); + } + + return found; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_list_view, container, false); + messageTextView = (TextView) root.findViewById(R.id.messageTextView); + addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites); + if (adapterKind != null) { + resultsListView = (ListView) root.findViewById(R.id.resultsListView); + switch (adapterKind) { + case STOPS: + resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + /* + * Casting because of Javamerda + * @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener + */ + Stop busStop = (Stop) parent.getItemAtPosition(position); + mListener.createFragmentForStop(busStop.ID); + } + }); + + //set the textviewMessage + setTextViewMessage(getString(R.string.results)); + break; + case ARRIVALS: + resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + String routeName; + + Route r = (Route) parent.getItemAtPosition(position); + routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay()); + if (routeName == null) { + routeName = r.getNameForDisplay(); + } + if (r.destinazione == null || r.destinazione.length() == 0) { + Toast.makeText(getContext(), + getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), + getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show(); + } + } + }); + String displayName = getArguments().getString(STOP_TITLE); + setTextViewMessage(String.format( + getString(R.string.passages), displayName)); + break; + default: + throw new IllegalStateException("Argument passed was not of a supported type"); + } + + String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW); + if (probablemessage != null) { + //Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage); + messageTextView.setText(probablemessage); + messageTextView.setVisibility(View.VISIBLE); + } + + } else + Log.d(getString(R.string.list_fragment_debug), "No content root for fragment"); + return root; + } + + public boolean isFragmentForTheSameStop(Palina p) { + return adapterKind.equals(FragmentKind.ARRIVALS) && getTag().equals(getFragmentTag(p)); + } + + public static String getFragmentTag(Palina p) { + return p.ID; + } + + + @Override + public void onResume() { + super.onResume(); + //Log.d(getString(R.string.list_fragment_debug),"Fragment restored, saved listAdapter is "+(mListAdapter)); + if (mListAdapter != null) { + + ListAdapter adapter = mListAdapter; + mListAdapter = null; + setListAdapter(adapter); + } + if (mListInstanceState != null) { + Log.d("resultsListView", "trying to restore instance state"); + resultsListView.onRestoreInstanceState(mListInstanceState); + } + switch (adapterKind) { + case ARRIVALS: + resultsListView.setOnScrollListener(new CommonScrollListener(mListener, true)); + fabutton.show(); + break; + case STOPS: + resultsListView.setOnScrollListener(new CommonScrollListener(mListener, false)); + break; + default: + //NONE + } + mListener.readyGUIfor(adapterKind); + + } + + @Override + public void onPause() { + if (adapterKind.equals(FragmentKind.ARRIVALS)) { + SwipeRefreshLayout reflay = (SwipeRefreshLayout) getActivity().findViewById(R.id.listRefreshLayout); + reflay.setEnabled(false); + Log.d("BusTO Fragment " + this.getTag(), "RefreshLayout disabled"); + } + super.onPause(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof FragmentListener) { + mListener = (FragmentListener) context; + fabutton = (FloatingActionButton) getActivity().findViewById(R.id.floatingActionButton); + } else { + throw new RuntimeException(context.toString() + + " must implement ResultFragmentListener"); + } + + } + + + @Override + public void onDetach() { + mListener = null; + if (fabutton != null) + fabutton.show(); + super.onDetach(); + } + + + @Override + public void onDestroyView() { + resultsListView = null; + //Log.d(getString(R.string.list_fragment_debug), "called onDestroyView"); + getArguments().putString(MESSAGE_TEXT_VIEW, messageTextView.getText().toString()); + super.onDestroyView(); + } + + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + Log.d("ResultListFragment", "onViewStateRestored"); + if (savedInstanceState != null) { + mListInstanceState = savedInstanceState.getParcelable(LIST_STATE); + Log.d("ResultListFragment", "listInstanceStatePresent :" + mListInstanceState); + } + } + + public void setListAdapter(ListAdapter adapter) { + boolean hadAdapter = mListAdapter != null; + mListAdapter = adapter; + if (resultsListView != null) { + resultsListView.setAdapter(adapter); + resultsListView.setVisibility(View.VISIBLE); + } + } + + /** + * Set the message textView + * @param message the whole message to write in the textView + */ + public void setTextViewMessage(String message) { + messageTextView.setText(message); + switch (adapterKind) { + case ARRIVALS: + addToFavorites.setClickable(true); + addToFavorites.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // add/remove the stop in the favorites + mListener.toggleLastStopToFavorites(); + } + }); + break; + case STOPS: + addToFavorites.setClickable(false); + break; + } + + + messageTextView.setVisibility(View.VISIBLE); + } +} \ No newline at end of file diff --git a/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java b/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java --- a/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java +++ b/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java @@ -1,120 +1,120 @@ -/* - 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 . - */ - -package it.reyboz.bustorino.middleware; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.os.AsyncTask; -import android.widget.Toast; -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.backend.Stop; - -/** - * Handler to add or remove or toggle a Stop in your favorites - */ -public class AsyncStopFavoriteAction extends AsyncTask { - private Context context; - - /** - * Kind of actions available - */ - public enum Action { ADD, REMOVE, TOGGLE }; - - /** - * Action chosen - * - * Note that TOGGLE is not converted to ADD or REMOVE. - */ - private Action action; - - /** - * Constructor - * - * @param context - * @param action - */ - public AsyncStopFavoriteAction(Context context, Action action) { - this.context = context.getApplicationContext(); - this.action = action; - } - - @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 = new UserDB(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 { - // remove - result = UserDB.deleteStop(stop, db); - } - - // please sir, close the door - db.close(); - } - - return result; - } - - /** - * Callback fired when everything was done - * - * @param result - */ - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - - if(result) { - // 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 { - // 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(); - } - } -} +/* + 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 . + */ + +package it.reyboz.bustorino.middleware; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import android.widget.Toast; +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.Stop; + +/** + * Handler to add or remove or toggle a Stop in your favorites + */ +public class AsyncStopFavoriteAction extends AsyncTask { + private Context context; + + /** + * Kind of actions available + */ + public enum Action { ADD, REMOVE, TOGGLE }; + + /** + * Action chosen + * + * Note that TOGGLE is not converted to ADD or REMOVE. + */ + private Action action; + + /** + * Constructor + * + * @param context + * @param action + */ + public AsyncStopFavoriteAction(Context context, Action action) { + this.context = context.getApplicationContext(); + this.action = action; + } + + @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 = new UserDB(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 { + // remove + result = UserDB.deleteStop(stop, db); + } + + // please sir, close the door + db.close(); + } + + return result; + } + + /** + * Callback fired when everything was done + * + * @param result + */ + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + if(result) { + // 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 { + // 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(); + } + } +} diff --git a/src/it/reyboz/bustorino/middleware/UserDB.java b/src/it/reyboz/bustorino/middleware/UserDB.java --- a/src/it/reyboz/bustorino/middleware/UserDB.java +++ b/src/it/reyboz/bustorino/middleware/UserDB.java @@ -1,274 +1,274 @@ -/* - BusTO ("backend" components) - Copyright (C) 2016 Ludovico Pavesi - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -package it.reyboz.bustorino.middleware; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.content.Context; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.backend.StopsDBInterface; - -public class UserDB extends SQLiteOpenHelper { - public static final int DATABASE_VERSION = 1; - private static final String DATABASE_NAME = "user.db"; - static final String TABLE_NAME = "favorites"; - private final Context c; // needed during upgrade - private final static String[] usernameColumnNameAsArray = {"username"}; - public final static String[] getFavoritesColumnNamesAsArray = {"ID", "username"}; - - public UserDB(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - this.c = context; - } - - @Override - public void onCreate(SQLiteDatabase db) { - // exception intentionally left unhandled - db.execSQL("CREATE TABLE favorites (ID TEXT PRIMARY KEY NOT NULL, username TEXT)"); - - if(OldDB.doesItExist(this.c)) { - upgradeFromOldDatabase(db); - } - } - - private void upgradeFromOldDatabase(SQLiteDatabase newdb) { - OldDB old; - try { - old = new OldDB(this.c); - } catch(IllegalStateException e) { - // can't create database => it doesn't really exist, no matter what doesItExist() says - return; - } - - int ver = old.getOldVersion(); - - /* version 8 was the previous version, OldDB "upgrades" itself to 1337 but unless the app - * has crashed midway through the upgrade and the user is retrying, that should never show - * up here. And if it does, try to recover favorites anyway. - * Versions < 8 already got dropped during the update process, so let's do the same. - * - * Edit: Android runs getOldVersion() then, after a while, onUpgrade(). Just to make it - * more complicated. Workaround added in OldDB. - */ - if(ver >= 8) { - ArrayList ID = new ArrayList<>(); - ArrayList username = new ArrayList<>(); - int len; - int len2; - - try { - Cursor c = old.getReadableDatabase().rawQuery("SELECT busstop_ID, busstop_username FROM busstop WHERE busstop_isfavorite = 1 ORDER BY busstop_name ASC", new String[] {}); - - int zero = c.getColumnIndex("busstop_ID"); - int one = c.getColumnIndex("busstop_username"); - - while(c.moveToNext()) { - try { - ID.add(c.getString(zero)); - } catch(Exception e) { - // no ID = can't add this - continue; - } - - if(c.getString(one) == null || c.getString(one).length() <= 0) { - username.add(null); - } else { - username.add(c.getString(one)); - } - } - - c.close(); - old.close(); - } catch(Exception ignored) { - // there's no hope, go ahead and nuke old database. - } - - len = ID.size(); - len2 = username.size(); - if(len2 < len) { - len = len2; - } - - - if (len > 0) { - - try { - Stop stopStopStopStopStop; - for (int i = 0; i < len; i++) { - stopStopStopStopStop = new Stop(ID.get(i)); - stopStopStopStopStop.setStopUserName(username.get(i)); - addOrUpdateStop(stopStopStopStopStop, newdb); - } - } catch(Exception ignored) { - // partial data is better than no data at all, no transactions here - } - } - } - - if(!OldDB.destroy(this.c)) { - // TODO: notify user somehow? - Log.e("UserDB", "Failed to delete old database, you should really uninstall and reinstall the app. Unfortunately I have no way to tell the user."); - } - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // nothing to do yet - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // nothing to do yet - } - - /** - * 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) { - boolean found = false; - - try { - Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopId}, null, null, null); - - if(c.moveToNext()) { - found = true; - } - - c.close(); - } catch(SQLiteException ignored) { - // don't care - } - - return found; - } - - /** - * 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 String getStopUserName(SQLiteDatabase db, String stopID) { - String username = null; - - try { - Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopID}, null, null, null); - - if(c.moveToNext()) { - username = c.getString(c.getColumnIndex("username")); - } - c.close(); - } catch(SQLiteException ignored) {} - - return username; - } - - public static List getFavorites(SQLiteDatabase db, StopsDBInterface dbi) { - List l = new ArrayList<>(); - Stop s; - String stopID, stopUserName; - - try { - Cursor c = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray, null, null, null, null, null, null); - int colID = c.getColumnIndex("ID"); - int colUser = c.getColumnIndex("username"); - - while(c.moveToNext()) { - stopUserName = c.getString(colUser); - stopID = c.getString(colID); - - s = dbi.getAllFromID(stopID); - - if(s == null) { - // can't find it in database - l.add(new Stop(stopUserName, stopID, null, null, null)); - } else { - // setStopName() already does sanity checks - s.setStopUserName(stopUserName); - l.add(s); - } - } - - c.close(); - } catch(SQLiteException ignored) {} - - // comparison rules are too complicated to let SQLite do this (e.g. it outputs: 3234, 34, 576, 67, 8222) and stop name is in another database - Collections.sort(l); - - return l; - } - - public static boolean addOrUpdateStop(Stop s, SQLiteDatabase db) { - ContentValues cv = new ContentValues(); - long result = -1; - String un = s.getStopUserName(); - - cv.put("ID", s.ID); - // is there an username? - if(un == null) { - // no: see if it's in the database - cv.put("username", getStopUserName(db, s.ID)); - } else { - // yes: use it - cv.put("username", un); - } - - 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) {} - - // Android Studio suggested this unreadable replacement: return true if insert succeeded (!= -1), or try to update and return - return (result != -1) || updateStop(s, db); - } - - public static boolean updateStop(Stop s, SQLiteDatabase db) { - try { - ContentValues cv = new ContentValues(); - cv.put("username", s.getStopUserName()); - db.update(TABLE_NAME, cv, "ID = ?", new String[]{s.ID}); - return true; - } catch(SQLiteException e) { - return false; - } - } - - public static boolean deleteStop(Stop s, SQLiteDatabase db) { - try { - db.delete(TABLE_NAME, "ID = ?", new String[]{s.ID}); - return true; - } catch(SQLiteException e) { - return false; - } - } -} +/* + BusTO ("backend" components) + Copyright (C) 2016 Ludovico Pavesi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package it.reyboz.bustorino.middleware; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.backend.StopsDBInterface; + +public class UserDB extends SQLiteOpenHelper { + public static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "user.db"; + static final String TABLE_NAME = "favorites"; + private final Context c; // needed during upgrade + private final static String[] usernameColumnNameAsArray = {"username"}; + public final static String[] getFavoritesColumnNamesAsArray = {"ID", "username"}; + + public UserDB(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + this.c = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + // exception intentionally left unhandled + db.execSQL("CREATE TABLE favorites (ID TEXT PRIMARY KEY NOT NULL, username TEXT)"); + + if(OldDB.doesItExist(this.c)) { + upgradeFromOldDatabase(db); + } + } + + private void upgradeFromOldDatabase(SQLiteDatabase newdb) { + OldDB old; + try { + old = new OldDB(this.c); + } catch(IllegalStateException e) { + // can't create database => it doesn't really exist, no matter what doesItExist() says + return; + } + + int ver = old.getOldVersion(); + + /* version 8 was the previous version, OldDB "upgrades" itself to 1337 but unless the app + * has crashed midway through the upgrade and the user is retrying, that should never show + * up here. And if it does, try to recover favorites anyway. + * Versions < 8 already got dropped during the update process, so let's do the same. + * + * Edit: Android runs getOldVersion() then, after a while, onUpgrade(). Just to make it + * more complicated. Workaround added in OldDB. + */ + if(ver >= 8) { + ArrayList ID = new ArrayList<>(); + ArrayList username = new ArrayList<>(); + int len; + int len2; + + try { + Cursor c = old.getReadableDatabase().rawQuery("SELECT busstop_ID, busstop_username FROM busstop WHERE busstop_isfavorite = 1 ORDER BY busstop_name ASC", new String[] {}); + + int zero = c.getColumnIndex("busstop_ID"); + int one = c.getColumnIndex("busstop_username"); + + while(c.moveToNext()) { + try { + ID.add(c.getString(zero)); + } catch(Exception e) { + // no ID = can't add this + continue; + } + + if(c.getString(one) == null || c.getString(one).length() <= 0) { + username.add(null); + } else { + username.add(c.getString(one)); + } + } + + c.close(); + old.close(); + } catch(Exception ignored) { + // there's no hope, go ahead and nuke old database. + } + + len = ID.size(); + len2 = username.size(); + if(len2 < len) { + len = len2; + } + + + if (len > 0) { + + try { + Stop stopStopStopStopStop; + for (int i = 0; i < len; i++) { + stopStopStopStopStop = new Stop(ID.get(i)); + stopStopStopStopStop.setStopUserName(username.get(i)); + addOrUpdateStop(stopStopStopStopStop, newdb); + } + } catch(Exception ignored) { + // partial data is better than no data at all, no transactions here + } + } + } + + if(!OldDB.destroy(this.c)) { + // TODO: notify user somehow? + Log.e("UserDB", "Failed to delete old database, you should really uninstall and reinstall the app. Unfortunately I have no way to tell the user."); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // nothing to do yet + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // nothing to do yet + } + + /** + * 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) { + boolean found = false; + + try { + Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopId}, null, null, null); + + if(c.moveToNext()) { + found = true; + } + + c.close(); + } catch(SQLiteException ignored) { + // don't care + } + + return found; + } + + /** + * 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 String getStopUserName(SQLiteDatabase db, String stopID) { + String username = null; + + try { + Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopID}, null, null, null); + + if(c.moveToNext()) { + username = c.getString(c.getColumnIndex("username")); + } + c.close(); + } catch(SQLiteException ignored) {} + + return username; + } + + public static List getFavorites(SQLiteDatabase db, StopsDBInterface dbi) { + List l = new ArrayList<>(); + Stop s; + String stopID, stopUserName; + + try { + Cursor c = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray, null, null, null, null, null, null); + int colID = c.getColumnIndex("ID"); + int colUser = c.getColumnIndex("username"); + + while(c.moveToNext()) { + stopUserName = c.getString(colUser); + stopID = c.getString(colID); + + s = dbi.getAllFromID(stopID); + + if(s == null) { + // can't find it in database + l.add(new Stop(stopUserName, stopID, null, null, null)); + } else { + // setStopName() already does sanity checks + s.setStopUserName(stopUserName); + l.add(s); + } + } + + c.close(); + } catch(SQLiteException ignored) {} + + // comparison rules are too complicated to let SQLite do this (e.g. it outputs: 3234, 34, 576, 67, 8222) and stop name is in another database + Collections.sort(l); + + return l; + } + + public static boolean addOrUpdateStop(Stop s, SQLiteDatabase db) { + ContentValues cv = new ContentValues(); + long result = -1; + String un = s.getStopUserName(); + + cv.put("ID", s.ID); + // is there an username? + if(un == null) { + // no: see if it's in the database + cv.put("username", getStopUserName(db, s.ID)); + } else { + // yes: use it + cv.put("username", un); + } + + 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) {} + + // Android Studio suggested this unreadable replacement: return true if insert succeeded (!= -1), or try to update and return + return (result != -1) || updateStop(s, db); + } + + public static boolean updateStop(Stop s, SQLiteDatabase db) { + try { + ContentValues cv = new ContentValues(); + cv.put("username", s.getStopUserName()); + db.update(TABLE_NAME, cv, "ID = ?", new String[]{s.ID}); + return true; + } catch(SQLiteException e) { + return false; + } + } + + public static boolean deleteStop(Stop s, SQLiteDatabase db) { + try { + db.delete(TABLE_NAME, "ID = ?", new String[]{s.ID}); + return true; + } catch(SQLiteException e) { + return false; + } + } +}