Changeset View
Changeset View
Standalone View
Standalone View
src/it/reyboz/bustorino/ActivityFavorites.java
Show All 11 Lines | BusTO - Arrival times for Turin public transports. | ||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
GNU General Public License for more details. | GNU General Public License for more details. | ||||
You should have received a copy of the GNU General Public License | You should have received a copy of the GNU General Public License | ||||
along with this program. If not, see <http://www.gnu.org/licenses/>. | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
package it.reyboz.bustorino; | package it.reyboz.bustorino; | ||||
import android.database.Cursor; | import androidx.lifecycle.ViewModelProvider; | ||||
import androidx.loader.app.LoaderManager; | |||||
import androidx.loader.content.Loader; | |||||
import android.widget.*; | import android.widget.*; | ||||
import it.reyboz.bustorino.backend.Stop; | import it.reyboz.bustorino.backend.Stop; | ||||
import it.reyboz.bustorino.adapters.StopAdapter; | import it.reyboz.bustorino.adapters.StopAdapter; | ||||
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction; | import it.reyboz.bustorino.data.FavoritesViewModel; | ||||
import it.reyboz.bustorino.data.StopsDB; | |||||
import it.reyboz.bustorino.data.UserDB; | import it.reyboz.bustorino.data.UserDB; | ||||
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction; | |||||
import android.app.AlertDialog; | import android.app.AlertDialog; | ||||
import android.content.Context; | |||||
import android.content.DialogInterface; | import android.content.DialogInterface; | ||||
import android.os.AsyncTask; | |||||
import androidx.core.app.NavUtils; | import androidx.core.app.NavUtils; | ||||
import androidx.appcompat.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||
import androidx.appcompat.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||
import android.view.ContextMenu; | import android.view.ContextMenu; | ||||
import android.view.ContextMenu.ContextMenuInfo; | import android.view.ContextMenu.ContextMenuInfo; | ||||
import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||
import android.view.MenuInflater; | import android.view.MenuInflater; | ||||
import android.view.MenuItem; | import android.view.MenuItem; | ||||
import android.view.View; | import android.view.View; | ||||
import android.widget.AdapterView.AdapterContextMenuInfo; | import android.widget.AdapterView.AdapterContextMenuInfo; | ||||
import android.content.Intent; | import android.content.Intent; | ||||
import android.database.sqlite.SQLiteDatabase; | import android.database.sqlite.SQLiteDatabase; | ||||
import android.os.Bundle; | import android.os.Bundle; | ||||
import java.util.List; | import java.util.List; | ||||
public class ActivityFavorites extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> { | public class ActivityFavorites extends AppCompatActivity { | ||||
private ListView favoriteListView; | private ListView favoriteListView; | ||||
private SQLiteDatabase userDB; | private SQLiteDatabase userDB; | ||||
private EditText bus_stop_name; | private EditText busStopNameText; | ||||
@Override | @Override | ||||
protected void onCreate(Bundle savedInstanceState) { | protected void onCreate(Bundle savedInstanceState) { | ||||
super.onCreate(savedInstanceState); | super.onCreate(savedInstanceState); | ||||
setContentView(R.layout.activity_favorites); | setContentView(R.layout.activity_favorites); | ||||
// this should be done in onStarted and closed in onStop, but apparently onStarted is never run. | // this should be done in onStarted and closed in onStop, but apparently onStarted is never run. | ||||
this.userDB = new UserDB(getApplicationContext()).getWritableDatabase(); | this.userDB = new UserDB(getApplicationContext()).getWritableDatabase(); | ||||
ActionBar ab = getSupportActionBar(); | ActionBar ab = getSupportActionBar(); | ||||
assert ab != null; | assert ab != null; | ||||
ab.setIcon(R.drawable.ic_launcher); | ab.setIcon(R.drawable.ic_launcher); | ||||
ab.setDisplayHomeAsUpEnabled(true); // Back button | ab.setDisplayHomeAsUpEnabled(true); // Back button | ||||
favoriteListView = (ListView) findViewById(R.id.favoriteListView); | favoriteListView = (ListView) findViewById(R.id.favoriteListView); | ||||
favoriteListView.setOnItemClickListener((parent, view, position, 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); | |||||
FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class); | |||||
model.getFavorites().observe(this, this::showStops); | |||||
createFavoriteList(); | |||||
} | } | ||||
@Override | @Override | ||||
public void onCreateContextMenu(ContextMenu menu, View v, | public void onCreateContextMenu(ContextMenu menu, View v, | ||||
ContextMenuInfo menuInfo) { | ContextMenuInfo menuInfo) { | ||||
super.onCreateContextMenu(menu, v, menuInfo); | super.onCreateContextMenu(menu, v, menuInfo); | ||||
if (v.getId() == R.id.favoriteListView) { | if (v.getId() == R.id.favoriteListView) { | ||||
MenuInflater inflater = getMenuInflater(); | MenuInflater inflater = getMenuInflater(); | ||||
Show All 16 Lines | public class ActivityFavorites extends AppCompatActivity { | ||||
public boolean onContextItemSelected(MenuItem item) { | public boolean onContextItemSelected(MenuItem item) { | ||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item | AdapterContextMenuInfo info = (AdapterContextMenuInfo) item | ||||
.getMenuInfo(); | .getMenuInfo(); | ||||
Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position); | Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position); | ||||
switch (item.getItemId()) { | switch (item.getItemId()) { | ||||
case R.id.action_favourite_entry_delete: | case R.id.action_favourite_entry_delete: | ||||
new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE, | |||||
// remove the stop from the favorites in background | new AsyncStopFavoriteAction.ResultListener() { | ||||
new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE, new AsyncStopFavoriteAction.ResultListener() { | |||||
@Override | @Override | ||||
public void doStuffWithResult(Boolean result) { | public void doStuffWithResult(Boolean result) { | ||||
//Update favorites list | |||||
createFavoriteList(); | |||||
} | } | ||||
}).execute(busStop); | }).execute(busStop); | ||||
return true; | return true; | ||||
case R.id.action_rename_bus_stop_username: | case R.id.action_rename_bus_stop_username: | ||||
showBusStopUsernameInputDialog(busStop); | showBusStopUsernameInputDialog(busStop); | ||||
return true; | return true; | ||||
case R.id.action_view_on_map: | case R.id.action_view_on_map: | ||||
final String theGeoUrl = busStop.getGeoURL(); | final String theGeoUrl = busStop.getGeoURL(); | ||||
if(theGeoUrl==null){ | if(theGeoUrl==null){ | ||||
//doesn't have a position | //doesn't have a position | ||||
Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_position,Toast.LENGTH_SHORT).show(); | Toast.makeText(getApplicationContext(),R.string.cannot_show_on_map_no_position,Toast.LENGTH_SHORT).show(); | ||||
return true; | return true; | ||||
} | } | ||||
// start ActivityMap with these extras in intent | // start ActivityMap with these extras in intent | ||||
Intent intent = new Intent(ActivityFavorites.this, ActivityMap.class); | Intent intent = new Intent(ActivityFavorites.this, ActivityMap.class); | ||||
Bundle b = new Bundle(); | Bundle b = new Bundle(); | ||||
b.putDouble("lat", busStop.getLatitude()); | double lat, lon; | ||||
b.putDouble("lon", busStop.getLongitude()); | if (busStop.getLatitude()!=null) | ||||
lat = busStop.getLatitude(); | |||||
else lat = 200; | |||||
if (busStop.getLongitude()!=null) | |||||
lon = busStop.getLongitude(); | |||||
else lon = 200; | |||||
b.putDouble("lat", lat); | |||||
b.putDouble("lon",lon); | |||||
b.putString("name", busStop.getStopDefaultName()); | b.putString("name", busStop.getStopDefaultName()); | ||||
b.putString("ID", busStop.ID); | b.putString("ID", busStop.ID); | ||||
intent.putExtras(b); | intent.putExtras(b); | ||||
startActivity(intent); | startActivity(intent); | ||||
return true; | return true; | ||||
default: | default: | ||||
return super.onContextItemSelected(item); | return super.onContextItemSelected(item); | ||||
} | } | ||||
} | } | ||||
void createFavoriteList() { | |||||
// TODO: memoize default list, query only user names every time? | void showStops(List<Stop> busStops){ | ||||
new AsyncGetFavorites(getApplicationContext(), this.userDB).execute(); | // 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); | |||||
ImageView angeryBusImageView = (ImageView) findViewById(R.id.angeryBusImageView); | |||||
angeryBusImageView.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, busStops)); | |||||
} | } | ||||
public void showBusStopUsernameInputDialog(final Stop busStop) { | public void showBusStopUsernameInputDialog(final Stop busStop) { | ||||
AlertDialog.Builder builder = new AlertDialog.Builder(this); | AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||
LayoutInflater inflater = this.getLayoutInflater(); | LayoutInflater inflater = this.getLayoutInflater(); | ||||
View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null); | View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null); | ||||
bus_stop_name = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name); | busStopNameText = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name); | ||||
bus_stop_name.setText(busStop.getStopDisplayName()); | busStopNameText.setText(busStop.getStopDisplayName()); | ||||
bus_stop_name.setHint(busStop.getStopDefaultName()); | busStopNameText.setHint(busStop.getStopDefaultName()); | ||||
builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title)); | builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title)); | ||||
builder.setView(renameDialogLayout); | builder.setView(renameDialogLayout); | ||||
builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { | builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { | ||||
@Override | @Override | ||||
public void onClick(DialogInterface dialog, int which) { | public void onClick(DialogInterface dialog, int which) { | ||||
String busStopUsername = bus_stop_name.getText().toString(); | String busStopUsername = busStopNameText.getText().toString(); | ||||
String oldUserName = busStop.getStopUserName(); | String oldUserName = busStop.getStopUserName(); | ||||
// changed to none | // changed to none | ||||
if(busStopUsername.length() == 0) { | if(busStopUsername.length() == 0) { | ||||
// unless it was already empty, set new | // unless it was already empty, set new | ||||
if(oldUserName != null) { | if(oldUserName != null) { | ||||
busStop.setStopUserName(null); | busStop.setStopUserName(null); | ||||
UserDB.updateStop(busStop, userDB); | //UserDB.updateStop(busStop, userDB); | ||||
createFavoriteList(); | //UserDB.notifyContentProvider(getApplicationContext()); | ||||
} | } | ||||
} else { // changed to something | } else { // changed to something | ||||
// something different? | // something different? | ||||
if(oldUserName == null || !busStopUsername.equals(oldUserName)) { | if(!busStopUsername.equals(oldUserName)) { | ||||
busStop.setStopUserName(busStopUsername); | busStop.setStopUserName(busStopUsername); | ||||
UserDB.updateStop(busStop, userDB); | //UserDB.updateStop(busStop, userDB); | ||||
createFavoriteList(); | //UserDB.notifyContentProvider(getApplicationContext()); | ||||
} | } | ||||
} | } | ||||
new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.UPDATE, | |||||
new AsyncStopFavoriteAction.ResultListener() { | |||||
@Override | |||||
public void doStuffWithResult(Boolean result) { | |||||
//Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show(); | |||||
} | |||||
}).execute(busStop); | |||||
} | } | ||||
}); | }); | ||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { | builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { | ||||
@Override | @Override | ||||
public void onClick(DialogInterface dialog, int which) { | public void onClick(DialogInterface dialog, int which) { | ||||
dialog.cancel(); | dialog.cancel(); | ||||
} | } | ||||
}); | }); | ||||
builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() { | builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() { | ||||
@Override | @Override | ||||
public void onClick(DialogInterface dialog, int which) { | public void onClick(DialogInterface dialog, int which) { | ||||
// delete user name from database | // delete user name from database | ||||
busStop.setStopUserName(null); | busStop.setStopUserName(null); | ||||
UserDB.updateStop(busStop, userDB); | UserDB.updateStop(busStop, userDB); | ||||
createFavoriteList(); | |||||
} | } | ||||
}); | }); | ||||
builder.show(); | builder.show(); | ||||
} | } | ||||
/** | /** | ||||
* This one runs. onStart instead gets ignored for no reason whatsoever. | * This one runs. onStart instead gets ignored for no reason whatsoever. | ||||
* | * | ||||
* @see <a href="https://i.stack.imgur.com/SAX9I.png">Android Activity Lifecycle</a> | * @see <a href="https://i.stack.imgur.com/SAX9I.png">Android Activity Lifecycle</a> | ||||
*/ | */ | ||||
@Override | @Override | ||||
protected void onStop() { | protected void onStop() { | ||||
super.onStop(); | super.onStop(); | ||||
this.userDB.close(); | this.userDB.close(); | ||||
} | } | ||||
/* | |||||
@Override | |||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) { | |||||
return null; | |||||
} | |||||
@Override | |||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { | |||||
} | |||||
@Override | |||||
public void onLoaderReset(Loader<Cursor> loader) { | |||||
} | |||||
private class AsyncGetFavorites extends AsyncTask<Void, Void, List<Stop>> { | private class AsyncGetFavorites extends AsyncTask<Void, Void, List<Stop>> { | ||||
private Context c; | private Context c; | ||||
private SQLiteDatabase userDB; | private SQLiteDatabase userDB; | ||||
AsyncGetFavorites(Context c, SQLiteDatabase userDB) { | AsyncGetFavorites(Context c, SQLiteDatabase userDB) { | ||||
this.c = c; | this.c = c; | ||||
this.userDB = userDB; | this.userDB = userDB; | ||||
} | } | ||||
@Override | @Override | ||||
protected List<Stop> doInBackground(Void... voids) { | protected List<Stop> doInBackground(Void... voids) { | ||||
StopsDB stopsDB = new StopsDB(c); | StopsDB stopsDB = new StopsDB(c); | ||||
stopsDB.openIfNeeded(); | stopsDB.openIfNeeded(); | ||||
List<Stop> busStops = UserDB.getFavorites(this.userDB, stopsDB); | List<Stop> busStops = UserDB.getFavorites(this.userDB, stopsDB); | ||||
stopsDB.closeIfNeeded(); | stopsDB.closeIfNeeded(); | ||||
return busStops; | return busStops; | ||||
} | } | ||||
@Override | @Override | ||||
protected void onPostExecute(List<Stop> busStops) { | protected void onPostExecute(List<Stop> 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); | |||||
ImageView angeryBusImageView = (ImageView) findViewById(R.id.angeryBusImageView); | |||||
angeryBusImageView.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); | |||||
} | } | ||||
} | } | ||||
*/ | |||||
} | } |
Public contents are in Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA) or GNU Free Documentation License (at your option) unless otherwise noted. · Contact / Register