package com.thebluealliance.androidclient.activities; import com.thebluealliance.androidclient.NfcUris; import com.thebluealliance.androidclient.R; import com.thebluealliance.androidclient.ShareUris; import com.thebluealliance.androidclient.TBAAndroid; import com.thebluealliance.androidclient.TbaLogger; import com.thebluealliance.androidclient.Utilities; import com.thebluealliance.androidclient.accounts.AccountController; import com.thebluealliance.androidclient.adapters.DialogListWithIconsAdapter; import com.thebluealliance.androidclient.adapters.ViewTeamFragmentPagerAdapter; import com.thebluealliance.androidclient.datafeed.CacheableDatafeed; import com.thebluealliance.androidclient.datafeed.status.TBAStatusController; import com.thebluealliance.androidclient.di.components.DaggerFragmentComponent; import com.thebluealliance.androidclient.di.components.FragmentComponent; import com.thebluealliance.androidclient.di.components.HasFragmentComponent; import com.thebluealliance.androidclient.helpers.ConnectionDetector; import com.thebluealliance.androidclient.helpers.TeamHelper; import com.thebluealliance.androidclient.interfaces.YearsParticipatedUpdate; import com.thebluealliance.androidclient.listeners.ClickListenerModule; import com.thebluealliance.androidclient.subscribers.SubscriberModule; import com.thebluealliance.androidclient.subscribers.YearsParticipatedDropdownSubscriber; import com.thebluealliance.androidclient.types.ModelType; import com.thebluealliance.androidclient.views.SlidingTabs; import android.Manifest; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.RequiresPermission; import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.Html; import android.view.MenuItem; import android.view.View; import android.widget.ListAdapter; import android.widget.TextView; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import javax.inject.Inject; import butterknife.Bind; import butterknife.ButterKnife; import permissions.dispatcher.NeedsPermission; import permissions.dispatcher.OnShowRationale; import permissions.dispatcher.PermissionRequest; import permissions.dispatcher.RuntimePermissions; import rx.schedulers.Schedulers; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @RuntimePermissions public class ViewTeamActivity extends MyTBASettingsActivity implements ViewPager.OnPageChangeListener, View.OnClickListener, HasFragmentComponent, YearsParticipatedUpdate { public static final String EXTRA_TEAM_KEY = "team_key"; public static final String TEAM_YEAR = "team_year"; public static final String SELECTED_YEAR = "year"; public static final String SELECTED_TAB = "tab"; public static final String CURRENT_PHOTO_URI = "current_photo_uri"; private static final String PREF_MEDIA_SNACKBAR_DISMISSED = "pref_media_snackbar_dismissed"; private static final int CHOOSE_IMAGE_REQUEST = 42; private static final int TAKE_PICTURE_REQUEST = 43; @Bind(R.id.year_selector_container) View mYearSelectorContainer; @Bind(R.id.year_selector_subtitle_container) View mYearSelectorSubtitleContainer; @Bind(R.id.year_selector_title) TextView mYearSelectorTitle; @Bind(R.id.year_selector_subtitle) TextView mYearSelectorSubtitle; @Bind(R.id.view_pager) ViewPager mPager; private Snackbar mMediaSnackbar; private FragmentComponent mComponent; private int mCurrentSelectedYearPosition = -1, mSelectedTab = -1; private String mCurrentPhotoUri; private int[] mYearsParticipated; @Inject TBAStatusController mStatusController; @Inject CacheableDatafeed mDatafeed; @Inject AccountController mAccountController; // Should come in the format frc#### private String mTeamKey; private int mYear; ViewTeamFragmentPagerAdapter mAdapter; public static Intent newInstance(Context context, String teamKey) { System.out.println("making intent for " + teamKey); Intent intent = new Intent(context, ViewTeamActivity.class); intent.putExtra(EXTRA_TEAM_KEY, teamKey); return intent; } public static Intent newInstance(Context context, String teamKey, int year) { Intent intent = new Intent(context, ViewTeamActivity.class); intent.putExtra(EXTRA_TEAM_KEY, teamKey); intent.putExtra(TEAM_YEAR, year); return intent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTeamKey = getIntent().getStringExtra(EXTRA_TEAM_KEY); if (mTeamKey == null) { throw new IllegalArgumentException("ViewTeamActivity must be created with a team key!"); } setModelKey(mTeamKey, ModelType.TEAM); setShareEnabled(true); setContentView(R.layout.activity_view_team); ButterKnife.bind(this); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); if (savedInstanceState != null) { if (savedInstanceState.containsKey(SELECTED_TAB)) { mSelectedTab = savedInstanceState.getInt(SELECTED_TAB); } if (savedInstanceState.containsKey(SELECTED_YEAR)) { mYear = savedInstanceState.getInt(SELECTED_YEAR); } if (savedInstanceState.containsKey(CURRENT_PHOTO_URI)) { mCurrentPhotoUri = savedInstanceState.getString(CURRENT_PHOTO_URI); } } else { int maxYear = mStatusController.getMaxCompYear(); if (getIntent() != null && getIntent().getExtras() != null && getIntent().getExtras().containsKey(TEAM_YEAR)) { mYear = getIntent().getIntExtra(TEAM_YEAR, maxYear); } else { mYear = maxYear; } mSelectedTab = 0; } mPager.setOffscreenPageLimit(3); mPager.setPageMargin(Utilities.getPixelsFromDp(this, 16)); // We will notify the fragments of the year later mAdapter = new ViewTeamFragmentPagerAdapter(getSupportFragmentManager(), mTeamKey, mYear); mPager.setAdapter(mAdapter); SlidingTabs tabs = (SlidingTabs) findViewById(R.id.tabs); tabs.setViewPager(mPager); tabs.setOnPageChangeListener(this); ViewCompat.setElevation(tabs, getResources().getDimension(R.dimen.toolbar_elevation)); if (!ConnectionDetector.isConnectedToInternet(this)) { showWarningMessage(BaseActivity.WARNING_OFFLINE); } mDatafeed.fetchTeamYearsParticipated(mTeamKey, null) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .subscribe(new YearsParticipatedDropdownSubscriber(this)); // We can call this even though the years participated haven't been loaded yet. // The years won't be shown yet; this just shows the team number in the toolbar. setupActionBar(); boolean wasMediaSnackbarDismissed = PreferenceManager.getDefaultSharedPreferences(this) .getBoolean(PREF_MEDIA_SNACKBAR_DISMISSED, false); if (!wasMediaSnackbarDismissed && mAccountController.isMyTbaEnabled()) { mMediaSnackbar = createSnackbar(Html.fromHtml(getString(R.string.imgur_media_snackbar_message)), Snackbar.LENGTH_INDEFINITE); mMediaSnackbar.setAction(R.string.imgur_media_snackbar_Action_dismiss, (view) -> { }); mMediaSnackbar.setCallback(new Snackbar.Callback() { @Override public void onDismissed(Snackbar snackbar, int event) { markMediaSnackbarAsDismissed(); } }).show(); } } @Override protected void onResume() { super.onResume(); setShareUri(String.format(ShareUris.URI_TEAM, TeamHelper.getTeamNumber(mTeamKey), mYear)); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(SELECTED_YEAR, mYear); outState.putInt(SELECTED_TAB, mSelectedTab); if (mCurrentPhotoUri != null) { outState.putString(CURRENT_PHOTO_URI, mCurrentPhotoUri); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); ViewTeamActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); } @Override public void onCreateNavigationDrawer() { useActionBarToggle(false); encourageLearning(false); } private void setupActionBar() { ActionBar bar = getSupportActionBar(); if (bar != null) { bar.setDisplayHomeAsUpEnabled(true); bar.setDisplayShowTitleEnabled(false); String teamNumber = mTeamKey.replace("frc", ""); mYearSelectorTitle.setText(String.format(getString(R.string.team_actionbar_title), teamNumber)); // If we call this and the years participated haven't been loaded yet, don't try to use them if (mYearsParticipated != null && mYearsParticipated.length > 0) { mYearSelectorSubtitleContainer.setVisibility(View.VISIBLE); final Dialog dialog = makeDialogForYearSelection(R.string.select_year, mYearsParticipated); mYearSelectorContainer.setOnClickListener(v -> dialog.show()); } else { // If there are no valid years, hide the subtitle and disable clicking mYearSelectorSubtitleContainer.setVisibility(View.GONE); mYearSelectorContainer.setOnClickListener(null); } } } private Dialog makeDialogForYearSelection(@StringRes int titleResId, int[] dropdownItems) { // Create an array of strings from the int years String[] years = new String[dropdownItems.length]; for (int i = 0; i < years.length; i++) { years[i] = String.valueOf(dropdownItems[i]); } Resources res = getResources(); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(res.getString(titleResId)); builder.setItems(years, (dialog, which) -> { onYearSelected(which); }); return builder.create(); } private void updateTeamYearSelector(int selectedPosition) { if (selectedPosition < 0 || selectedPosition >= mYearsParticipated.length) { return; } mYearSelectorSubtitle.setText(String.valueOf(mYearsParticipated[selectedPosition])); } @Override public void updateYearsParticipated(int[] years) { mYearsParticipated = years; // If we received a desired year in the intent, find the index of that year if it exists int requestedYearIndex = 0; for (int i = 0; i < years.length; i++) { if (years[i] == mYear) { requestedYearIndex = i; } } // Refresh action bar; this will the year subtitle if there are no valid ones setupActionBar(); onYearSelected(requestedYearIndex); } private void onYearSelected(int position) { // Only handle this if the year has actually changed if (position == mCurrentSelectedYearPosition) { return; } // Bounds checking! if (position < 0 || position >= mYearsParticipated.length) { return; } mCurrentSelectedYearPosition = position; updateTeamYearSelector(position); int newYear = mYearsParticipated[mCurrentSelectedYearPosition]; if (newYear == mYear) { return; } mYear = newYear; setBeamUri(String.format(NfcUris.URI_TEAM_IN_YEAR, mTeamKey, mYear)); setShareUri(String.format(ShareUris.URI_TEAM, TeamHelper.getTeamNumber(mTeamKey), mYear)); mAdapter.updateYear(mYear); mAdapter.notifyDataSetChanged(); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { if (isDrawerOpen()) { closeDrawer(); return true; } // If this tasks exists in the back stack, it will be brought to the front and all other activities // will be destroyed. HomeActivity will be delivered this intent via onNewIntent(). startActivity(HomeActivity.newInstance(this, R.id.nav_item_teams).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); return true; } return super.onOptionsItemSelected(item); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { mSelectedTab = position; switch (position) { case ViewTeamFragmentPagerAdapter.TAB_INFO: syncFabVisibilityWithMyTbaEnabled(true); setupFabForMyTbaSettingsTab(); break; case ViewTeamFragmentPagerAdapter.TAB_MEDIA: syncFabVisibilityWithMyTbaEnabled(true); setFabColor(R.color.accent); setFabDrawable(R.drawable.ic_add_a_photo_white_24dp); break; default: hideFab(true); break; } } @Override public void onPageScrollStateChanged(int state) { } @Override protected boolean onFabClick() { switch (mSelectedTab) { case ViewTeamFragmentPagerAdapter.TAB_MEDIA: // If the device doesn't have a camera, send them straight to the image picker if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { selectImage(); } else { final String[] items = new String[]{"Take picture", "Choose image"}; final Integer[] icons = new Integer[]{R.drawable.ic_photo_camera_black_24dp, R.drawable.ic_photo_library_black_24dp}; ListAdapter adapter = new DialogListWithIconsAdapter(this, items, icons); new AlertDialog.Builder(this) .setTitle("Add team image") .setAdapter(adapter, (dialog, position) -> { switch (position) { case 0: // take picture ViewTeamActivityPermissionsDispatcher.takePictureWithCheck(this); dialog.cancel(); break; case 1: // select from gallery selectImage(); dialog.cancel(); break; } }) .show(); } // Dismiss the awareness snackbar when the fab is clicked if (mMediaSnackbar != null) { mMediaSnackbar.dismiss(); markMediaSnackbarAsDismissed(); } return true; } return false; } private void selectImage() { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(Intent.createChooser(intent, "Select Picture"), CHOOSE_IMAGE_REQUEST); } } @RequiresPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) void takePicture() { if (!checkHasStoragePermission()) { TbaLogger.e("Permission WRITE_EXTERNAL_STORAGE not granted"); showSnackbar(R.string.error_taking_picture); return; } Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // Error occurred while creating the File // TODO handle this TbaLogger.e("Unable to create image file", ex); showSnackbar(R.string.error_taking_picture); } // Continue only if the File was successfully created if (photoFile != null) { takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); startActivityForResult(takePictureIntent, TAKE_PICTURE_REQUEST); } else { TbaLogger.w("Unable to open file for photo"); showSnackbar(R.string.error_taking_picture); } } else { TbaLogger.w("Unable to resolve "); showSnackbar(R.string.error_taking_picture); } } @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE) void showRationaleForStorage(final PermissionRequest request) { new AlertDialog.Builder(this) .setTitle(R.string.camera_permission_title) .setMessage(R.string.camera_permission_rationale) .setPositiveButton(R.string.allow, (dialog, button) -> request.proceed()) .setNegativeButton(R.string.deny, (dialog, button) -> request.cancel()) .show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CHOOSE_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) { if (data != null) { Uri uri = data.getData(); TbaLogger.i("Uri: " + uri.toString()); // Pass off the URI to ConfirmImageSuggestionActivity, it will handle uploading // and suggesting the appropriate image startActivity(ConfirmImageSuggestionActivity.newIntent(this, uri, mTeamKey, mYear)); } } else if (requestCode == TAKE_PICTURE_REQUEST && resultCode == RESULT_OK) { // Tell Media Scanner about the new photo sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(mCurrentPhotoUri))); // Off to ConfirmImageSuggestionActivity! startActivity(ConfirmImageSuggestionActivity.newIntent(this, Uri.parse(mCurrentPhotoUri), mTeamKey, mYear)); } } @RequiresPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); storageDir = new File(storageDir, "The Blue Alliance"); storageDir.mkdirs(); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); mCurrentPhotoUri = "file:" + image.getAbsolutePath(); return image; } private boolean checkHasStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return PERMISSION_GRANTED == checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); } else { // No runtime permissions before API 23, so everything is "granted" as it's declared // in the app Manifest return true; } } private void markMediaSnackbarAsDismissed() { PreferenceManager.getDefaultSharedPreferences(ViewTeamActivity.this) .edit().putBoolean(PREF_MEDIA_SNACKBAR_DISMISSED, true).commit(); } public FragmentComponent getComponent() { if (mComponent == null) { TBAAndroid application = ((TBAAndroid) getApplication()); mComponent = DaggerFragmentComponent.builder() .applicationComponent(application.getComponent()) .datafeedModule(application.getDatafeedModule()) .binderModule(application.getBinderModule()) .databaseWriterModule(application.getDatabaseWriterModule()) .gceModule(application.getGceModule()) .subscriberModule(new SubscriberModule(this)) .clickListenerModule(new ClickListenerModule(this)) .build(); } return mComponent; } @Override public void inject() { getComponent().inject(this); } }