package com.thebluealliance.androidclient.activities;
import com.thebluealliance.androidclient.R;
import com.thebluealliance.androidclient.TbaLogger;
import com.thebluealliance.androidclient.helpers.TeamHelper;
import com.thebluealliance.androidclient.imgur.ImgurSuggestionService;
import com.thebluealliance.androidclient.imgur.ImgurUtils;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import butterknife.Bind;
import butterknife.ButterKnife;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class ConfirmImageSuggestionActivity extends AppCompatActivity implements View.OnClickListener {
private static final String EXTRA_IMAGE_URI = "image_uri";
private static final String EXTRA_TEAM_KEY = "team_key";
private static final String EXTRA_YEAR = "year";
private static final String SAVED_TEMP_FILE_PATH = "saved_file_url";
@Bind(R.id.toolbar) Toolbar mToolbar;
@Bind(R.id.header) TextView mHeader;
@Bind(R.id.image) ImageView mImageView;
@Bind(R.id.progress) ProgressBar mProgressBar;
@Bind(R.id.confirm_fab) FloatingActionButton mConfirmFab;
@Bind(R.id.cancel_fab) FloatingActionButton mCancelFab;
private Uri mUri;
private String mTeamKey;
private int mYear;
private File mImageFile;
public static Intent newIntent(Context context, Uri imageUri, String teamKey, int year) {
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_IMAGE_URI, imageUri);
extras.putString(EXTRA_TEAM_KEY, teamKey);
extras.putInt(EXTRA_YEAR, year);
Intent intent = new Intent(context, ConfirmImageSuggestionActivity.class);
intent.putExtras(extras);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_confirm_image_suggestion);
ButterKnife.bind(this);
mConfirmFab.setOnClickListener(this);
mCancelFab.setOnClickListener(this);
// Disable the "confirm" FAB until we have a valid file to submit
mConfirmFab.setEnabled(false);
ViewCompat.setElevation(mToolbar, getResources().getDimension(R.dimen.toolbar_elevation));
setSupportActionBar(mToolbar);
// TODO don't use hardcoded string
getSupportActionBar().setTitle("Confirm suggestion");
Bundle extras = getIntent().getExtras() == null ? new Bundle() : getIntent().getExtras();
if (!extras.containsKey(EXTRA_IMAGE_URI)
|| !extras.containsKey(EXTRA_TEAM_KEY)
|| !extras.containsKey(EXTRA_YEAR)) {
throw new IllegalArgumentException("ConfirmImageSuggestionActivity is missing required extras");
}
mUri = extras.getParcelable(EXTRA_IMAGE_URI);
mTeamKey = extras.getString(EXTRA_TEAM_KEY);
mYear = extras.getInt(EXTRA_YEAR);
// Validate intent extras
if (!TeamHelper.validateTeamKey(mTeamKey)) {
throw new IllegalArgumentException("Invalid team key!");
}
if (mUri == null) {
throw new IllegalArgumentException("URI is null!");
}
if (savedInstanceState != null && savedInstanceState.containsKey(SAVED_TEMP_FILE_PATH)) {
mImageFile = new File(savedInstanceState.getString(SAVED_TEMP_FILE_PATH));
}
// Set up the header view, which displays "Team NUMBER (YEAR)"
mHeader.setText(getString(R.string.imgur_confirm_image_header, TeamHelper.getTeamNumber(mTeamKey), mYear));
// Don't begin caching and loading the image until layout is complete; the ImageView must
// have a defined width and height in order to compute inSampleSize for loading the bitmap
ViewTreeObserver vto = mImageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
cacheAndLoadImage();
}
});
}
/**
* Loads the image from {@code mUri} into our cache directory and displays it in this activity.
* The caching part is important because the image at {@code mUri} is not guaranteed to be in
* local storage; for instance, it could be an image that needs to be loaded from Google Drive.
* We need to store it in a local file so that {@link ImgurSuggestionService}
* can upload it properly.
* <p>
* This should not be called until after initial layout is complete; loading the Bitmap into
* memory efficiently requires that we know how big the target ImageView so we can scale it
* properly during the decoding process.
*/
private void cacheAndLoadImage() {
Observable<File> fileObservable;
if (mImageFile != null) {
fileObservable = Observable.just(mImageFile);
} else {
fileObservable = Observable.just(mUri).map((uri) -> {
mImageFile = ImgurUtils.createFile(uri, ConfirmImageSuggestionActivity.this);
if (mImageFile == null) {
// TODO error handling!
TbaLogger.e("Image was null!");
}
return mImageFile;
});
}
fileObservable.map((file) -> {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream stream = new BufferedInputStream(new FileInputStream(file));
BitmapFactory.decodeStream(stream, null, options);
stream.close();
options.inSampleSize = calculateInSampleSize(options, mImageView.getWidth(), mImageView.getHeight());
options.inJustDecodeBounds = false;
stream = new BufferedInputStream(new FileInputStream(file));
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
stream.close();
return bitmap;
} catch (IOException e) {
throw new RuntimeException("Error reading bitmap");
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bitmap -> {
mImageView.setImageBitmap(bitmap);
// Fade ImageView in and ProgressBar out
mImageView.setAlpha(0.0f);
mImageView.animate().alpha(1.0f).setDuration(500).start();
mProgressBar.setAlpha(1.0f);
mProgressBar.animate().alpha(0.0f).setDuration(500).start();
mConfirmFab.setEnabled(true);
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mImageFile != null) {
outState.putString(SAVED_TEMP_FILE_PATH, mImageFile.getAbsolutePath());
}
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.confirm_fab:
Toast.makeText(this, "Your image will be uploaded in the background!", Toast.LENGTH_SHORT).show();
startService(ImgurSuggestionService.newIntent(this, mImageFile.getAbsolutePath(), mTeamKey, mYear));
this.finish();
break;
case R.id.cancel_fab:
Toast.makeText(this, "Submission cancelled", Toast.LENGTH_SHORT).show();
// Delete the cached image file to free up storage space
if (mImageFile != null) {
mImageFile.delete();
}
this.finish();
break;
}
}
}