package rectangledbmi.com.pittsburghrealtimetracker; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.File; import java.util.Calendar; import java.util.Set; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import rectangledbmi.com.pittsburghrealtimetracker.handlers.Constants; import rectangledbmi.com.pittsburghrealtimetracker.retrofit.patapi.PATAPI; import rectangledbmi.com.pittsburghrealtimetracker.selection.NotificationMessage; import rectangledbmi.com.pittsburghrealtimetracker.selection.RouteSelection; import rectangledbmi.com.pittsburghrealtimetracker.world.Route; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import rx.Observable; import rx.Observer; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; import timber.log.Timber; /** * This is the main activity of the Realtime Tracker... */ public class SelectTransit extends AppCompatActivity implements NavigationDrawerFragment.BusListCallbacks, SelectionFragment.BusSelectionInteraction { private static final String LINES_LAST_UPDATED = "lines_last_updated"; /** * Fragment managing the behaviors, interactions and presentation of the navigation drawer. */ private NavigationDrawerFragment mNavigationDrawerFragment; /** * Fragment that contains data from navigation drawer */ private SelectionFragment selectionFragment; /** * Port Authority API Client made through Retrofit * * @since 46 */ private PATAPI patApiClient; /** * Reference to the main activity's Layout. * * @since 55 */ private DrawerLayout mainLayout; /** * subject to show toast messages. * @since 70 */ private PublishSubject<NotificationMessage> toastSubject; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); restoreActionBar(); buildPATAPI(); setContentView(R.layout.activity_select_transit); FragmentManager fragmentManager = getSupportFragmentManager(); mNavigationDrawerFragment = (NavigationDrawerFragment) fragmentManager.findFragmentById(R.id.navigation_drawer); checkSDCardData(); // Set up the drawer. mNavigationDrawerFragment.setUp( R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); mainLayout = (DrawerLayout) findViewById(R.id.drawer_layout); selectionFragment = (SelectionFragment) fragmentManager.findFragmentById(R.id.map); // create a publish subject for displaying toasts toastSubject = PublishSubject.create(); toastSubject.asObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(toastMessageObserver()); enableHttpResponseCache(); } /** * In addition to destroying the {@link SelectTransit}, it will also complete the toast subject. * @since 70 */ @Override protected void onDestroy() { toastSubject.onCompleted(); super.onDestroy(); } /** * Builds the PAT API Client from a Rest Adapter * * @since 46 */ private void buildPATAPI() { // use a date converter Gson gson = new GsonBuilder() .setDateFormat(Constants.DATE_FORMAT_PARSE) .create(); final OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.SECONDS) .build(); // build the restadapter final Retrofit retrofit = new Retrofit.Builder() .baseUrl(getString(R.string.api_url)) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(okHttpClient) .build(); // sets the PAT API patApiClient = retrofit.create(PATAPI.class); } public PATAPI getPatApiClient() { return patApiClient; } /** * Checks if the stored polylines directory is present and clears if we hit a friday or if the * saved day of the week is higher than the current day of the week. * * @since 32 */ private void checkSDCardData() { File data = getFilesDir(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); Long lastUpdated = sp.getLong(LINES_LAST_UPDATED, -1); Timber.d(data.getName()); if (data.mkdirs()) Timber.d("Created data storage"); File lineInfo = new File(data, "/lineinfo"); if (data.mkdirs()) Timber.d("created line info folder in storage"); Timber.d(Long.toString(lastUpdated)); if (lastUpdated != -1 && ((System.currentTimeMillis() - lastUpdated) / 1000 / 60 / 60) > 24) { if (lineInfo.exists()) { Calendar c = Calendar.getInstance(); int day = c.get(Calendar.DAY_OF_WEEK); Calendar o = Calendar.getInstance(); o.setTimeInMillis(lastUpdated); int oldDay = o.get(Calendar.DAY_OF_WEEK); if (day == Calendar.FRIDAY || oldDay >= day) { File[] files = lineInfo.listFiles(); sp.edit().putLong(LINES_LAST_UPDATED, System.currentTimeMillis()).apply(); if (files != null) { for (File file : files) { if (file.delete()) Timber.d("%s deleted", file.getName()); } } } } } if (lineInfo.listFiles() == null || lineInfo.listFiles().length == 0) { sp.edit().putLong(LINES_LAST_UPDATED, System.currentTimeMillis()).apply(); } } private void enableHttpResponseCache() { try { long httpCacheSize = 10485760; // 10 MiB File fetch = getExternalCacheDir(); if (fetch == null) { fetch = getCacheDir(); } File httpCacheDir = new File(fetch, "http"); Class.forName("android.net.http.HttpResponseCache") .getMethod("install", File.class, long.class) .invoke(null, httpCacheDir, httpCacheSize); } catch (Exception httpResponseCacheNotAvailable) { Timber.e("HTTP response cache is unavailable."); } } /** * Checks the state of the route on the map. If it is not on the map, the relevant info will be * added. Otherwise, it will be unselected on the map * * @param route the bus route selected * @since 43 */ @Override public void onSelectBusRoute(@NonNull Route route) { selectionFragment.onSelectBusRoute(route); } @Override public void onDeselectBusRoute(Route route) { selectionFragment.onDeselectBusRoute(route); } public void restoreActionBar() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); if (toolbar != null) { try { setSupportActionBar(toolbar); } catch (Throwable e) { Snackbar.make(mainLayout, "Material Design bugged out on your device. Please report this to the " + "Play Store Email if this pops up.", Snackbar.LENGTH_SHORT).show(); // Toast.makeText(this, "Material Design bugged out on your device. Please report this to the Play Store Email if this pops up.", Toast.LENGTH_SHORT).show(); } } try { ActionBar t = getSupportActionBar(); if (t != null) t.setDisplayHomeAsUpEnabled(true); } catch (NullPointerException e) { Snackbar.make(mainLayout, "Material Design bugged out on your device. Please report this to the " + "Play Store Email if this pops up.", Snackbar.LENGTH_SHORT).show(); } } //dunno... @Override public boolean onCreateOptionsMenu(Menu menu) { if (!mNavigationDrawerFragment.isDrawerOpen()) { // Only show items in the action bar relevant to this screen // if the drawer is not showing. Otherwise, let the drawer // decide what to show in the action bar. getMenuInflater().inflate(R.menu.select_transit, menu); // restoreActionBar(); return true; } return super.onCreateOptionsMenu(menu); } /** * Handles action bar item clicks. The action bar will automatically handle clicks on the * home/up button so long as you specify a parent activity in the AndroidManifest.xml. * * @param item the menu item clicked * @return true if the option is found? */ @Override public boolean onOptionsItemSelected(MenuItem item) { Timber.d("Running SelectTransit's onOptionsItemSelected"); // TODO: Perhaps we should be using onClick events in the XML like onClickAppDetails() if (mNavigationDrawerFragment != null && mNavigationDrawerFragment.getActionBarDrawerToggle() != null && mNavigationDrawerFragment.getActionBarDrawerToggle().onOptionsItemSelected(item)) { Timber.d("Hamburger menu selected - will either close or open drawer"); return true; } int id = item.getItemId(); if (id == R.id.action_select_buses && mNavigationDrawerFragment != null) { Timber.d("Select Buses in Menu Dropdown clicked - opens the drawer"); mNavigationDrawerFragment.openDrawer(); return true; } else if (id == R.id.action_about) { Timber.d("About Button Clicked. Will open the about page"); Intent intent = new Intent(this, AboutActivity.class); startActivity(intent); return true; } else if (id == R.id.action_clear) { clearSelection(); return true; } return super.onOptionsItemSelected(item); } public void clearSelection() { File lineInfo = new File(getFilesDir(), "/lineinfo"); Timber.d("cleared files: %s", lineInfo.getAbsolutePath()); if (lineInfo.exists()) { File[] files = lineInfo.listFiles(); if (files != null) { for (File file : files) //noinspection ResultOfMethodCallIgnored file.delete(); } } mNavigationDrawerFragment.clearSelection(); selectionFragment.clearSelection(); } /** * Show a toast message. * @since 47 * @param message the message to show as a toast * @param duration the duration that the message shows */ @Override public void showToast(String message, int duration) { if (toastSubject == null) { return; } toastSubject.onNext(NotificationMessage.create(message, duration)); } /** * Workaround to show a toast from {@link #toastMessageObserver()} * @param message the message to show * @param duration the duration of the message */ private void showToastInternal(String message, int duration) { Toast.makeText(this, message, duration).show(); } /** * {@link Observer} to show a toast using Notification Messages. * * @since 70 */ private Observer<NotificationMessage> toastMessageObserver() { return new Observer<NotificationMessage>() { @Override public void onCompleted() { Timber.i("Completed toast observer"); } @Override public void onError(Throwable e) { Timber.e(e, "Toast Observable encountered an error"); } @Override public void onNext(NotificationMessage notificationMessage) { if (notificationMessage == null) { Timber.d("No notification message was sent"); } else { Timber.d("printing toast message: %s", notificationMessage.getMessage()); // android linter goes crazy if passing a normal int into // Toast.makeText in the line below...... (Why) must use // showToastInternal() to generate a toast as a workaround. showToastInternal( notificationMessage.getMessage(), notificationMessage.getLength()); } } }; } public void makeSnackbar(@NonNull String string, int showLength, @NonNull String action, @NonNull View.OnClickListener listener) { if (string.length() == 0) return; Snackbar.make(mainLayout, string, showLength) .setAction(action, listener) .show(); } public void onBackPressed() { if (!mNavigationDrawerFragment.closeDrawer()) super.onBackPressed(); } @Override public void showOkDialog(String message, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder(SelectTransit.this) .setMessage(message) .setPositiveButton("OK", okListener) .setNegativeButton("Cancel", null) .create() .show(); } @Override @NonNull public Route getSelectedRoute(String routeNumber) { return mNavigationDrawerFragment.getSelectedRoute(routeNumber); } @Override @NonNull public Set<String> getSelectedRoutes() { return mNavigationDrawerFragment.getSelectedRoutes(); } @NonNull @Override public Observable<RouteSelection> getSelectionSubject() { return mNavigationDrawerFragment.getListObservable(); } @Override public void openPermissionsPage() { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } /** * Click Event for the * {@link rectangledbmi.com.pittsburghrealtimetracker.R.menu#select_transit}'s Detour * Information. * <p> * Maybe we should be moving item item events in {@link #onOptionsItemSelected(MenuItem)} to * an onClick like this method. * * @param item the application details item */ public void onClickDetourInfo(MenuItem item) { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.detour_url))); startActivity(browserIntent); } /** * Click Event for the * {@link rectangledbmi.com.pittsburghrealtimetracker.R.menu#select_transit}'s Application * Details. * <p> * Maybe we should be moving item item events in {@link #onOptionsItemSelected(MenuItem)} to an * onClick like this method. * * @param item the application details item */ public void onClickAppDetails(MenuItem item) { openPermissionsPage(); } }