Java tutorial
/* * Copyright (c) 2017 Washington State Department of Transportation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package gov.wa.wsdot.android.wsdot.ui.ferries.departures; import android.Manifest; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.design.widget.AppBarLayout; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v4.view.MenuItemCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.AlertDialog; import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationServices; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; import javax.inject.Inject; import dagger.android.AndroidInjection; import gov.wa.wsdot.android.wsdot.R; import gov.wa.wsdot.android.wsdot.shared.FerriesScheduleDateItem; import gov.wa.wsdot.android.wsdot.shared.FerriesTerminalItem; import gov.wa.wsdot.android.wsdot.ui.BaseActivity; import gov.wa.wsdot.android.wsdot.ui.WsdotApplication; import gov.wa.wsdot.android.wsdot.ui.ferries.FerrySchedulesViewModel; import gov.wa.wsdot.android.wsdot.ui.ferries.departures.vesselwatch.VesselWatchFragment; import gov.wa.wsdot.android.wsdot.util.MyLogger; import gov.wa.wsdot.android.wsdot.util.TabsAdapter; public class FerriesRouteSchedulesDayDeparturesActivity extends BaseActivity implements AdapterView.OnItemSelectedListener, LocationListener { private final String TAG = FerriesRouteSchedulesDayDeparturesActivity.class.getSimpleName(); private boolean initLoad = true; protected static final int DAY_SPINNER_ID = 0; protected static final int SAILING_SPINNER_ID = 1; static final private int MENU_ITEM_STAR = 0; private boolean mIsStarred = false; private TabLayout mTabLayout; private List<Class<? extends Fragment>> tabFragments = new ArrayList<>(); private ViewPager mViewPager; private AppBarLayout mAppBar; private gov.wa.wsdot.android.wsdot.util.TabsAdapter mTabsAdapter; private Toolbar mToolbar; private Tracker mTracker; private AppCompatSpinner mSailingSpinner; private AppCompatSpinner mDaySpinner; private int mScheduleId; private int mTerminalIndex = 0; private int mDayIndex = 0; private static FerriesTerminalItem mTerminalItem = new FerriesTerminalItem(); private static ArrayList<FerriesScheduleDateItem> mScheduleDateItems; private static FerrySchedulesViewModel scheduleViewModel; private static FerryTerminalViewModel terminalViewModel; private static FerryTerminalCameraViewModel terminalCameraViewModel; private ArrayAdapter<CharSequence> mDayOfWeekArrayAdapter; private ArrayAdapter<CharSequence> mSailingsArrayAdapter; boolean isAccessibilityEnabled = false; boolean isExploreByTouchEnabled = false; protected Location mLastLocation; private final int REQUEST_ACCESS_FINE_LOCATION = 100; @Inject ViewModelProvider.Factory viewModelFactory; @Override public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); if (savedInstanceState != null) { initLoad = savedInstanceState.getBoolean("initLoad", true); mDayIndex = savedInstanceState.getInt("dayIndex", 0); mTerminalIndex = savedInstanceState.getInt("terminalIndex", 0); } AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE); if (am != null) { isAccessibilityEnabled = am.isEnabled(); isExploreByTouchEnabled = am.isTouchExplorationEnabled(); } Bundle args = getIntent().getExtras(); String title = args.getString("title"); mScheduleId = args.getInt("scheduleId"); mIsStarred = args.getInt("isStarred") != 0; setContentView(R.layout.activity_ferry_sailings); mViewPager = findViewById(R.id.pager); mAppBar = findViewById(R.id.appbar); mToolbar = findViewById(R.id.toolbar); mToolbar.setTitle(title); setSupportActionBar(mToolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } // set up the sailings spinner mSailingSpinner = this.findViewById(R.id.sailing_spinner); mSailingSpinner.setOnItemSelectedListener(this); mSailingSpinner.setId(SAILING_SPINNER_ID); mSailingsArrayAdapter = new ArrayAdapter<>(this, R.layout.simple_spinner_dropdown_item_white); mSailingsArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSailingSpinner.setAdapter(mSailingsArrayAdapter); // set up the day spinner mDaySpinner = this.findViewById(R.id.day_spinner); mDaySpinner.setOnItemSelectedListener(this); mDaySpinner.setId(DAY_SPINNER_ID); mDayOfWeekArrayAdapter = new ArrayAdapter<>(this, R.layout.simple_spinner_dropdown_item_white); mDayOfWeekArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mDaySpinner.setAdapter(mDayOfWeekArrayAdapter); // set up tab layout mTabLayout = findViewById(R.id.tab_layout); mTabLayout.setTabGravity(TabLayout.GRAVITY_FILL); // Add tab titles and their corresponding fragments to the fragment list. tabFragments.add(mTabLayout.getTabCount(), FerriesRouteSchedulesDayDeparturesFragment.class); mTabLayout.addTab(mTabLayout.newTab().setText("Times")); tabFragments.add(mTabLayout.getTabCount(), FerriesTerminalCameraFragment.class); mTabLayout.addTab(mTabLayout.newTab().setText("Cameras")); tabFragments.add(mTabLayout.getTabCount(), VesselWatchFragment.class); mTabLayout.addTab(mTabLayout.newTab().setText("Vessel Watch")); mTabsAdapter = new TabsAdapter(this, tabFragments, getSupportFragmentManager(), mTabLayout.getTabCount()); mViewPager.setAdapter(mTabsAdapter); mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout)); mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { mViewPager.setCurrentItem(tab.getPosition()); if (tab.getText().equals("Cameras")) { mTracker = ((WsdotApplication) getApplication()).getDefaultTracker(); mTracker.setScreenName("/Ferries/Departures/" + tab.getText()); mTracker.send(new HitBuilders.ScreenViewBuilder().build()); MyLogger.crashlyticsLog("Ferries", "Tap", "FerriesRouteSchedulesDayDeparturesActivity " + tab.getText(), 1); } if (tab.getText().equals("Vessel Watch")) { mTracker = ((WsdotApplication) getApplication()).getDefaultTracker(); mTracker.setScreenName("/Ferries/Departures/" + tab.getText()); mTracker.send(new HitBuilders.ScreenViewBuilder().build()); MyLogger.crashlyticsLog("Ferries", "Tap", "FerriesRouteSchedulesDayDeparturesActivity " + tab.getText(), 1); mAppBar.setExpanded(true, true); AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams(); params.setScrollFlags(0); } else { if (!isAccessibilityEnabled && !isExploreByTouchEnabled) { mAppBar.setExpanded(true); // set expanded true so scroll flags take effect. Not sure why this works. AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams(); params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED); } } } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); terminalViewModel = ViewModelProviders.of(this, viewModelFactory).get(FerryTerminalViewModel.class); terminalCameraViewModel = ViewModelProviders.of(this, viewModelFactory) .get(FerryTerminalCameraViewModel.class); scheduleViewModel = ViewModelProviders.of(this, viewModelFactory).get(FerrySchedulesViewModel.class); scheduleViewModel.init(mScheduleId); scheduleViewModel.getResourceStatus().observe(this, resourceStatus -> { if (resourceStatus != null) { switch (resourceStatus.status) { case LOADING: break; case SUCCESS: break; case ERROR: break; } } }); scheduleViewModel.getDatesWithSailings().observe(this, dates -> { if (dates != null) { mScheduleDateItems = new ArrayList<>(dates); // only request location on init load if (initLoad) { requestLocation(); } else { setViewContent(); } } }); MyLogger.crashlyticsLog("Ferries", "Screen View", "FerriesRouteSchedulesDayDeparturesActivity " + title, 1); enableAds(getString(R.string.ferries_ad_target)); // Accessibility if (isAccessibilityEnabled || isExploreByTouchEnabled) { mAppBar.setExpanded(true, true); AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams(); params.setScrollFlags(0); } } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("dayIndex", mDayIndex); savedInstanceState.putInt("terminalIndex", mTerminalIndex); } /** * Updates global array adapter with new day strings from schedule * @param schedule ferry schedule data */ private void setDaySpinner(ArrayList<FerriesScheduleDateItem> schedule) { mDayOfWeekArrayAdapter.clear(); DateFormat dateFormat = new SimpleDateFormat("EEEE"); dateFormat.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); int numDates = schedule.size(); for (int i = 0; i < numDates; i++) { if (!schedule.get(i).getFerriesTerminalItem().isEmpty()) { mDayOfWeekArrayAdapter.add(dateFormat.format(schedule.get(i).getDate())); } } mDaySpinner.setSelection(mDayIndex); } /** * Updates global array adapter with new values from sailings * @param sailings A to B terminal sailings */ private void setRouteSpinner(ArrayList<FerriesTerminalItem> sailings) { mSailingsArrayAdapter.clear(); int numSailings = sailings.size(); for (int i = 0; i < numSailings; i++) { mSailingsArrayAdapter.add(sailings.get(i).getDepartingTerminalName() + " to " + sailings.get(i).getArrivingTerminalName()); } mSailingSpinner.setSelection(mTerminalIndex); } private void setViewContent() { mTerminalItem = mScheduleDateItems.get(mDayIndex).getFerriesTerminalItem().get(mTerminalIndex); setRouteSpinner(mScheduleDateItems.get(mDayIndex).getFerriesTerminalItem()); setDaySpinner(mScheduleDateItems); if (initLoad) { initLoad = false; terminalViewModel.loadDepartureTimesForTerminal(mTerminalItem); terminalCameraViewModel.loadTerminalCameras(mTerminalItem.getDepartingTerminalID(), "ferries"); } } /** * Callback for spinners. Gets the terminal item for the selected day and * requests the departure times for that day from the view model. */ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Boolean shouldUpdate; switch (parent.getId()) { case FerriesRouteSchedulesDayDeparturesActivity.SAILING_SPINNER_ID: shouldUpdate = mTerminalIndex != position; mTerminalIndex = position; break; case FerriesRouteSchedulesDayDeparturesActivity.DAY_SPINNER_ID: shouldUpdate = mDayIndex != position; mDayIndex = position; break; default: shouldUpdate = false; } // Don't bother updating the terminal if we didn't select anything new if (shouldUpdate) { mTerminalItem = mScheduleDateItems.get(mDayIndex).getFerriesTerminalItem().get(mTerminalIndex); terminalViewModel.setScrollToCurrent(true); terminalViewModel.loadDepartureTimesForTerminal(mTerminalItem); terminalCameraViewModel.loadTerminalCameras(mTerminalItem.getDepartingTerminalID(), "ferries"); } } @Override public void onNothingSelected(AdapterView<?> adapterView) { } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem menuItem_Star = menu.add(0, MENU_ITEM_STAR, menu.size(), R.string.description_star); MenuItemCompat.setShowAsAction(menuItem_Star, MenuItemCompat.SHOW_AS_ACTION_ALWAYS); if (mIsStarred) { menu.getItem(MENU_ITEM_STAR).setIcon(R.drawable.ic_menu_star_on); menu.getItem(MENU_ITEM_STAR).setTitle("Favorite checkbox, checked"); } else { menu.getItem(MENU_ITEM_STAR).setIcon(R.drawable.ic_menu_star); menu.getItem(MENU_ITEM_STAR).setTitle("Favorite checkbox, not checked"); } return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; case MENU_ITEM_STAR: MyLogger.crashlyticsLog("Ferries", "Tap", "FerriesRouteSchedulesDayDeparturesActivity star", 1); toggleStar(item); return true; } return super.onOptionsItemSelected(item); } private void toggleStar(MenuItem item) { Snackbar added_snackbar = Snackbar.make(findViewById(R.id.activity_ferry_sailings), R.string.add_favorite, Snackbar.LENGTH_SHORT); Snackbar removed_snackbar = Snackbar.make(findViewById(R.id.activity_ferry_sailings), R.string.remove_favorite, Snackbar.LENGTH_SHORT); added_snackbar.addCallback(new Snackbar.Callback() { @Override public void onShown(Snackbar snackbar) { super.onShown(snackbar); snackbar.getView().setContentDescription("added to favorites"); snackbar.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT); } }); removed_snackbar.addCallback(new Snackbar.Callback() { @Override public void onShown(Snackbar snackbar) { super.onShown(snackbar); snackbar.getView().setContentDescription("removed from favorites"); snackbar.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT); } }); if (mIsStarred) { item.setIcon(R.drawable.ic_menu_star); item.setTitle("Favorite checkbox, not checked"); scheduleViewModel.setIsStarredFor(mScheduleId, 0); removed_snackbar.show(); mIsStarred = false; } else { item.setIcon(R.drawable.ic_menu_star_on); item.setTitle("Favorite checkbox, checked"); scheduleViewModel.setIsStarredFor(mScheduleId, 1); added_snackbar.show(); mIsStarred = true; } } private int getTerminalIndexForTerminalClosestTo(double latitude, double longitude) { int closestTerminalIndex = 0; int minDistance = Integer.MAX_VALUE; int index = 0; for (FerriesTerminalItem terminalItem : mScheduleDateItems.get(mDayIndex).getFerriesTerminalItem()) { int distance = FerryHelper.getDistanceFromTerminal(terminalItem.getDepartingTerminalID(), latitude, longitude); if (distance < minDistance) { minDistance = distance; closestTerminalIndex = index; } index++; } return closestTerminalIndex; } // Location logic /** * Request location updates after checking permissions first. */ private void requestLocation() { if (ContextCompat.checkSelfPermission(FerriesRouteSchedulesDayDeparturesActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(FerriesRouteSchedulesDayDeparturesActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); boolean hasSeenRationale = settings.getBoolean("KEY_SEEN_FERRY_LOCATION_RATIONALE", false); if (!hasSeenRationale) { // Show explanation to user explaining why we need the permission AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Allow location services to let WSDOT select the nearest terminal."); builder.setTitle("Find Nearest Departure Terminal?"); builder.setPositiveButton("OK", (dialog, which) -> ActivityCompat.requestPermissions( FerriesRouteSchedulesDayDeparturesActivity.this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, REQUEST_ACCESS_FINE_LOCATION)); builder.setNegativeButton("no thanks", ((dialog, which) -> setViewContent())); AlertDialog dialog = builder.show(); dialog.getButton(AlertDialog.BUTTON_NEGATIVE) .setTextColor(getResources().getColor(R.color.primary_default)); dialog.getButton(AlertDialog.BUTTON_POSITIVE) .setTextColor(getResources().getColor(R.color.primary_default)); } else { setViewContent(); } SharedPreferences.Editor editor = settings.edit(); editor.putBoolean("KEY_SEEN_FERRY_LOCATION_RATIONALE", true); editor.apply(); } else { // No explanation needed, we can request the permission ActivityCompat.requestPermissions(FerriesRouteSchedulesDayDeparturesActivity.this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, REQUEST_ACCESS_FINE_LOCATION); } } else { // We have permission! LocationServices.getFusedLocationProviderClient(this).getLastLocation() .addOnSuccessListener(location -> { mLastLocation = location; if (mLastLocation != null) { mTerminalIndex = getTerminalIndexForTerminalClosestTo(mLastLocation.getLatitude(), mLastLocation.getLongitude()); } setViewContent(); }); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (grantResults.length > 0 || permissions.length > 0) { // Check if request was canceled. switch (requestCode) { case REQUEST_ACCESS_FINE_LOCATION: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission granted, try and get location again! requestLocation(); } else { // Permission was denied or request was cancelled // Just show the first terminal in the list setViewContent(); } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } } public void onLocationChanged(Location arg0) { // TODO Auto-generated method stub } }