Java tutorial
package com.pressurelabs.flowopensource; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewSwitcher; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.drive.Drive; import com.google.android.gms.drive.DriveApi; import com.google.android.gms.drive.DriveId; import com.google.android.gms.drive.MetadataChangeSet; import com.google.android.gms.drive.OpenFileActivityBuilder; import com.kobakei.ratethisapp.RateThisApp; import java.util.ArrayList; import java.util.Calendar; /** * Flow_app * * @author Robert Simoes, 2016-08-14 * Copyright (c) 2016, Robert Simoes All rights reserved. * * The Hub Screen provides a List of the User's current Flows along with some minor details. * The class allows for the creation and saving of new Flows, destruction of current ones, editing and renaming of Flows * and launching of the Flows into a new Activity */ public class TheHubActivity extends AppCompatActivity implements HubRecyclerViewAdapter.onCardClickListener, NavigationView.OnNavigationItemSelectedListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { /* Data Management and Export */ private GoogleApiClient mGoogleApiClient; private AppDataManager manager; // Manages the saving of data and Flow objects to internal storage /* Recycler View */ private RecyclerView recyclerView; private HubRecyclerViewAdapter adapter; private ArrayList<Flow> rvContent; /* Card Interactions */ private String menuState; private PopupWindow longClickPopup, editingPopup; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_the_hub); /* Set up ActionBar */ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_the_hub); setSupportActionBar(toolbar); /* Set up Navigation Drawer */ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.hub_drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); generateDrawerGreeting(navigationView); /* Set up recycler and Card View */ recyclerView = (RecyclerView) findViewById(R.id.recycler_view); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setItemAnimator(new DefaultItemAnimator()); if (savedInstanceState != null && !savedInstanceState.isEmpty()) { rvContent = savedInstanceState.getParcelableArrayList(AppConstants.RESTORED_USER_FLOWS); manager = savedInstanceState.getParcelable(AppConstants.RESTORED_DATA_MANAGER); } else { rvContent = new ArrayList<>(); manager = new AppDataManager(this); } menuState = AppConstants.MENU_ITEMS_NATIVE; mGoogleApiClient = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE) .addConnectionCallbacks(this).addOnConnectionFailedListener(this).build(); } private void generateDrawerGreeting(NavigationView view) { View header = view.getHeaderView(0); TextView greeting = (TextView) header.findViewById(R.id.ndrawer_date_greeting); String[] array = this.getResources().getStringArray(R.array.drawer_greeting); switch (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) { case Calendar.MONDAY: greeting.setText(array[0]); break; case Calendar.TUESDAY: greeting.setText(array[1]); break; case Calendar.WEDNESDAY: greeting.setText(array[2]); break; case Calendar.THURSDAY: greeting.setText(array[3]); break; case Calendar.FRIDAY: greeting.setText(array[4]); break; case Calendar.SATURDAY: greeting.setText(array[5]); break; case Calendar.SUNDAY: greeting.setText(array[6]); break; default: greeting.setText(array[7]); break; } } @Override public boolean onNavigationItemSelected(MenuItem menuItem) { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.hub_drawer_layout); switch (menuItem.getItemId()) { case R.id.action_tou: drawer.closeDrawer(GravityCompat.START); goToLicense(); return false; case R.id.action_support_devs: Toast.makeText(this, R.string.feature_not_ready, Toast.LENGTH_LONG).show(); return false; case R.id.action_export_data: exportDataToDrive(); return true; default: return false; } } /* Allows the menu items to appear in the toolbar */ @Override public boolean onPrepareOptionsMenu(final Menu menu) { menu.clear(); getMenuInflater().inflate(R.menu.menu_the_hub, menu); MenuItem newF = menu.findItem(R.id.action_new_flow); MenuItem deleteAllF = menu.findItem(R.id.action_delete_flows); if (menuState.equals(AppConstants.MENU_ITEMS_HIDE)) { newF.setVisible(false); deleteAllF.setVisible(false); } return super.onCreateOptionsMenu(menu); } /* Invokes methods based on the icon picked in the toolbar */ @Override public boolean onOptionsItemSelected(MenuItem item) { /* When the user selects one of the app bar items, the system calls your activity's onOptionsItemSelected() callback method, and passes a MenuItem object to indicate which item was clicked */ switch (item.getItemId()) { case R.id.action_delete_flows: deleteFlowsDialog(); return true; case R.id.action_new_flow: createNewFlow(); return true; case R.id.action_send_feedback: AppUtils.sendFeedback(this); return true; default: // If we got here, the user's action was not recognized. // Invoke the superclass to handle it. return super.onOptionsItemSelected(item); } } private void goToLicense() { startActivity(new Intent(TheHubActivity.this, LicenseActivity.class)); } @Override protected void onResume() { super.onResume(); menuState = AppConstants.MENU_ITEMS_NATIVE; invalidateOptionsMenu(); populateRecycleView(); if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE) .addConnectionCallbacks(this).addOnConnectionFailedListener(this).build(); } mGoogleApiClient.connect(); } /** Populates the RecycleView by asking for internal storage to be read, * determining whether the response data is valid, and rebuilding the * RecycleView if possible. */ private void populateRecycleView() { boolean savedContentAvailable = manager.hasData(); // Reads Internal Storage JSON file, receiving return in String Format if (savedContentAvailable) { new Runnable() { @Override public void run() { rebuildContent(); } }.run(); } else { /* If no data is avaliable from file, a new Array Adapter will be setup and feed a blank RecycleView */ adapter = new HubRecyclerViewAdapter(TheHubActivity.this, rvContent); // Create new adapter with Recycle View Content // adapter.setCardEditingCallback(this); recyclerView.setAdapter(adapter); } } /** Attempts to rebuild the RecycleView Content by asking for the AppDataManager to create * an ArrayList of it's available data from file to recreate the RecViewAdapter. * */ private void rebuildContent() { rvContent = manager.generateArrayList(); adapter = new HubRecyclerViewAdapter(TheHubActivity.this, rvContent); // Recreate FlowArrayAdapter and set // adapter.setCardEditingCallback(this); recyclerView.setAdapter(adapter); } /** Launches the process of creating a new Flow Object by * developing a Custom Dialog Box and determining valid * user input. * */ public void createNewFlow() { //Create edit text field for name entry final EditText nameInputET = new EditText(TheHubActivity.this); AlertDialog.Builder customDialog = generateCustomDialog(nameInputET); customDialog.setPositiveButton("Lets Roll", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (nameInputET.getText().toString().equals("")) { // Need to optimize this so that the dialog does NOT disappear and just display toast Toast.makeText(TheHubActivity.this, "Every Flow deserves a good name :(", Toast.LENGTH_LONG) .show(); createNewFlow(); //Recall the dialog } else { Flow newF = new Flow(nameInputET.getText().toString(), 0); if (adapter != null) { rvContent.add(newF); // Set the Flow Manager Index and add to List View Content adapter.notifyDataSetChanged(); } manager.save(newF.getUuid(), newF); } } }); customDialog.setNegativeButton("Nevermind", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); } }); customDialog.show(); } private AlertDialog.Builder generateCustomDialog(EditText nameInputET) { AlertDialog.Builder newFlowDialog = new AlertDialog.Builder(TheHubActivity.this); //Sets up Layout Parameters LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); params.setMarginStart(42); params.setMarginEnd(50); AppUtils.setNameInputFilters(nameInputET); //Adds the ET and params to the layout of the dialog box layout.addView(nameInputET, params); newFlowDialog.setTitle("Name your new Flow."); newFlowDialog.setView(layout); return newFlowDialog; } /** * Creates and prompts user for confirmation of deleting all * Flow's in the list view from file */ private void deleteFlowsDialog() { new AlertDialog.Builder(this).setTitle("ALL Flows will be deleted.").setMessage("This action is PERMANENT") .setCancelable(false).setPositiveButton("Understood", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { deleteAllFlowData(); Toast.makeText(TheHubActivity.this, "Those poor Flows. \nI hope you're proud of yourself", Toast.LENGTH_LONG).show(); } }).setNegativeButton("No Don't! It's a Trap!", null).show(); } /** Deletes All Flows visible in the RecView as well as * requests for all Flow data to be deleted from Internal Storage. * * @return boolean, confirmation of */ private void deleteAllFlowData() { rvContent.removeAll(rvContent); manager.deleteAllData(); adapter.notifyDataSetChanged(); } /** * Sends the user to the FlowSandbox Activity while passing the Flow that was clicked and it's * UUID for use in the next activity * @param clickedFlow flow that was clicked */ @Override public void onCardClick(Flow clickedFlow) { Intent i = new Intent(TheHubActivity.this, SandBoxActivity.class); i.putExtra(AppConstants.EXTRA_PASSING_UUID, clickedFlow.getUuid()); startActivity(i); } /** * Shows a popupmenu related to editing or deleting the Flow that was LongClicked * * @param longClickedFlow Flow represented by cardview longclicked * @param cardPosition position of cardview in adapter * @param cardViewClicked the cardview view object clicked * @return boolean representing consumption */ @Override public boolean onCardLongClick(Flow longClickedFlow, int cardPosition, View cardViewClicked) { return showLongClickPopUpMenu(longClickedFlow, cardPosition, cardViewClicked); } /** * Generates a Popup Menu with Two Actions Edit and Delete. * * Deleting the Flow removes the single card from the UI and also notifiers the AppDataManager to * delete from file * * Editing launches a renaming process * * @param longClickedFlow Flow represented by cardview longclicked * @param cardPosition position of cardview in adapter * @param cardViewClicked the cardview view object clicked * @return */ private boolean showLongClickPopUpMenu(final Flow longClickedFlow, final int cardPosition, final View cardViewClicked) { LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = layoutInflater.inflate(R.layout.popup_window_longclick, null); LinearLayout viewGroup = (LinearLayout) layout.findViewById(R.id.popup_longclick); // Creating the PopupWindow final PopupWindow popup = new PopupWindow(layout, RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); int dividerMargin = viewGroup.getDividerPadding(); // Top bottom int popupPadding = layout.getPaddingBottom(); int popupDisplayHeight = -(cardViewClicked.getHeight() - dividerMargin - popupPadding); // Prevents border popup.setBackgroundDrawable(new ColorDrawable()); popup.setFocusable(true); // Getting a reference to Close button, and close the popup when clicked. ImageView delete = (ImageView) layout.findViewById(R.id.popup_delete_item); delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /* Deletes current Flow from file and UI */ rvContent.remove(cardPosition); manager.delete(longClickedFlow.getUuid()); adapter.notifyItemRemoved(cardPosition); adapter.notifyItemRangeChanged(cardPosition, adapter.getItemCount()); popup.dismiss(); Snackbar bar = Snackbar.make(cardViewClicked, R.string.snackbar_hub_msg, Snackbar.LENGTH_LONG) .setAction("NO!!!", new View.OnClickListener() { @Override public void onClick(View v) { rvContent.add(cardPosition, longClickedFlow); manager.save(longClickedFlow.getUuid(), longClickedFlow); adapter.notifyItemInserted(cardPosition); } }); bar.show(); } }); ImageView edit = (ImageView) layout.findViewById(R.id.popup_edit_item); edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { popup.dismiss(); renameFlow(cardPosition, cardViewClicked); } }); // Displaying the popup at the specified location, + offsets. popup.showAsDropDown(cardViewClicked, cardViewClicked.getMeasuredWidth(), popupDisplayHeight, Gravity.TOP); longClickPopup = popup; return true; } /** * Hides the Options Menu and uses a ViewSwitcher to quick turn the exisiting TextView with the * Flow's name into an EditText for the user to rename. * * @param cardPosition position of cardview in adapter * @param cardViewClicked the cardview view object clicked */ private void renameFlow(final int cardPosition, final View cardViewClicked) { menuState = AppConstants.MENU_ITEMS_HIDE; invalidateOptionsMenu(); final ViewSwitcher switcher = (ViewSwitcher) cardViewClicked.findViewById(R.id.hub_rename_switcher); final EditText rename = (EditText) switcher.findViewById(R.id.hub_item_flow_rename); AppUtils.setNameInputFilters(rename); rename.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (rename.hasFocus()) { showEditPopupWindow(rename, cardViewClicked, switcher, cardPosition); } } }); switcher.showNext(); rename.requestFocus(); /* Forces keyboard */ } /** * Displays a popup window prompting the user to * * Confirm the changes to the name, saving the new name to file and updating the UI. * * Cancel the changes, returning the user back to the original state before editing * * @param newName new name to be used * @param cardPosition position of cardview in adapter * @param cardViewClicked the cardview view object clicked * @param switcher the viewSwitcher object used to rename */ private void showEditPopupWindow(final EditText newName, View cardViewClicked, final ViewSwitcher switcher, final int cardPosition) { LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = layoutInflater.inflate(R.layout.popup_window_editing, null); LinearLayout viewGroup = (LinearLayout) layout.findViewById(R.id.popup_editing); // Creating the PopupWindow final PopupWindow popupEditing = new PopupWindow(layout, RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); int dividerMargin = viewGroup.getDividerPadding(); // Top bottom int popupPadding = layout.getPaddingBottom(); int popupDisplayHeight = -(cardViewClicked.getHeight() - dividerMargin - popupPadding); // Prevents border from appearing outside popupwindow popupEditing.setBackgroundDrawable(new ColorDrawable()); popupEditing.setFocusable(false); // Getting a reference to Close button, and close the popup when clicked. ImageView confirmEdit = (ImageView) layout.findViewById(R.id.popup_confirm_item_changes); confirmEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Flow toChange = rvContent.get(cardPosition); if (newName.getText().toString().equals("")) { // Need to optimize this so that the dialog does NOT disappear and just display toast Toast.makeText(TheHubActivity.this, "This Flow needs a name!", Toast.LENGTH_LONG).show(); } else { toChange.setName(newName.getText().toString()); manager.overwrite(toChange.getUuid(), toChange); adapter.notifyDataSetChanged(); switcher.showNext(); menuState = AppConstants.MENU_ITEMS_NATIVE; invalidateOptionsMenu(); popupEditing.dismiss(); newName.clearFocus(); } } }); ImageView cancelEdit = (ImageView) layout.findViewById(R.id.popup_cancel_item_changes); cancelEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switcher.showNext(); menuState = AppConstants.MENU_ITEMS_NATIVE; invalidateOptionsMenu(); popupEditing.dismiss(); } }); // Displaying the popup at the specified location, + offsets. popupEditing.showAsDropDown(cardViewClicked, cardViewClicked.getMeasuredWidth(), popupDisplayHeight, Gravity.TOP); editingPopup = popupEditing; } /** * Popups must be removed before activity becomes invisible * Api Client must disconnect. */ @Override protected void onPause() { dismissPopups(); if (mGoogleApiClient != null) { mGoogleApiClient.disconnect(); } super.onPause(); } @Override public void onBackPressed() { dismissPopups(); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.hub_drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } super.onBackPressed(); } @Override protected void onStart() { super.onStart(); // Monitor launch times and interval from installation mGoogleApiClient.connect(); RateThisApp.onStart(this); RateThisApp.Config config = new RateThisApp.Config(10, 10); // Custom title ,message and buttons names config.setTitle(R.string.rate_app_title); config.setMessage(R.string.rate_app_message); config.setYesButtonText(R.string.rate); config.setNoButtonText(R.string.no_rate); config.setCancelButtonText(R.string.rate_cancel); RateThisApp.init(config); // If the criteria is satisfied, "Rate this app" dialog will be shown RateThisApp.showRateDialogIfNeeded(this); } @Override protected void onSaveInstanceState(Bundle outState) { outState.putParcelableArrayList(AppConstants.RESTORED_USER_FLOWS, rvContent); outState.putParcelable(AppConstants.RESTORED_DATA_MANAGER, manager); super.onSaveInstanceState(outState); } /** * Catches any popups still open to prevent window leaking when issuing new Intents */ private void dismissPopups() { if (longClickPopup != null && longClickPopup.isShowing()) { longClickPopup.setFocusable(false); longClickPopup.dismiss(); } if (editingPopup != null && editingPopup.isShowing()) { editingPopup.setFocusable(false); editingPopup.dismiss(); } } private void exportDataToDrive() { createNewDriveFile(); } private void createNewDriveFile() { final ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() { @Override public void onResult(DriveApi.DriveContentsResult result) { if (!result.getStatus().isSuccess()) { Toast.makeText(TheHubActivity.this, R.string.export_failed_msg, Toast.LENGTH_LONG).show(); return; } MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder().setMimeType("text/html") .build(); IntentSender intentSender = Drive.DriveApi.newCreateFileActivityBuilder() .setInitialMetadata(metadataChangeSet).setInitialDriveContents(result.getDriveContents()) .build(mGoogleApiClient); try { startIntentSenderForResult(intentSender, AppConstants.EXPORT_CREATOR_REQUEST_CODE, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { AppUtils.showMessage(TheHubActivity.this, "Data could not be exported"); } } }; Drive.DriveApi.newDriveContents(mGoogleApiClient).setResultCallback(contentsCallback); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case AppConstants.EXPORT_RESOLVE_CONNECTION_REQUEST_CODE: if (resultCode == RESULT_OK) { mGoogleApiClient.connect(); } break; case AppConstants.EXPORT_CREATOR_REQUEST_CODE: if (resultCode == RESULT_OK) { DriveId driveFileId = (DriveId) data .getParcelableExtra(OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID); writeDataExportToFile(driveFileId); } break; default: super.onActivityResult(requestCode, resultCode, data); break; } } private void writeDataExportToFile(DriveId driveFileId) { EditContentParams params = new EditContentParams( new ExportDataManager(TheHubActivity.this).readFileByInputStream(), driveFileId.asDriveFile()); new EditContentsAsyncTask(this).execute(params); } @Override public void onConnected(@Nullable Bundle bundle) { } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { /* Callback can be invoked if user has not previously authorized the app. */ if (connectionResult.hasResolution()) { try { connectionResult.startResolutionForResult(this, AppConstants.EXPORT_RESOLVE_CONNECTION_REQUEST_CODE); } catch (IntentSender.SendIntentException e) { // Unable to resolve, message user appropriately } } else { Toast.makeText(this, R.string.feedback_failed_msg, Toast.LENGTH_LONG).show(); GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), this, 0).show(); } } /** * Getter for the {@code GoogleApiClient}. */ public GoogleApiClient getGoogleApiClient() { return mGoogleApiClient; } }