/*
* Copyright 2015 Daniel Dittmar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package dan.dit.whatsthat.image;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import dan.dit.whatsthat.R;
import dan.dit.whatsthat.preferences.Language;
import dan.dit.whatsthat.preferences.Tongue;
import dan.dit.whatsthat.preferences.User;
import dan.dit.whatsthat.solution.Solution;
import dan.dit.whatsthat.system.store.WorkshopView;
import dan.dit.whatsthat.util.general.BuildException;
import dan.dit.whatsthat.util.general.VersionSafe;
import dan.dit.whatsthat.util.image.BitmapUtil;
import dan.dit.whatsthat.util.image.ImageUtil;
/**
* A class used by the workshop to allow relatively easily building an image
* bundle of images on the phone by selecting images from default galleries.
* It requires at least one Solution for each image and author information (at least a name).
* It offers the possibility to enter multiple solution words and multiple solutions for the supported
* languages. The interface is not the most pretty one but relatively robust and helpful, so it can be used
* by advanced users, though the primary use would be developers or for fan :) art.
* It does not support custom preferred or refused RiddleTypes, though the default ones belonging to the image
* are being calculated. It fits images inside square of 700x700 pixels, keeping aspect ratio to prevent
* too big images. It keeps the image's format.
* A bundle requires an origin which is the bundle's author, the one generating the bundle
* and a bundle name which is the file name and needs to be relatively unique (at least on the operating device)
* Created by daniel on 02.09.15.
*/
public class BundleCreator {
// Hi there. As this view controller offers on the fly validation and fast feedback for given inputs
// and was written in two days, the code is not really nice, I'm sorry.
private static final int ACCEPT_RESOURCE = R.drawable.a_green;
private static final int REFUSE_RESOURCE = R.drawable.x_red;
private static final int IMAGE_MAX_WIDTH = 700; // max dimensions of images in new bundle
private static final int IMAGE_MAX_HEIGHT = 700;
private static final int COMPRESSION = 20; // compression level between 0 and 100 (especially for .jpg images)
private static final int SNAPSHOT_WIDTH = 100; // displayed snapshot of currently selected bitmap
private static final int SNAPSHOT_HEIGHT = 100;
private final View mView;
private final ImageView mOriginStatus;
private final EditText mOriginName;
private final ImageView mBundleNameStatus;
private final EditText mBundleName;
private final TextView mProgressText;
private final Button mSave;
private final TextView mBitmapsDescr;
private final Button mBitmapsSelect;
private final ProgressBar mBitmapsSelectProgress;
private final ImageView mBitmapsStatus;
private final Activity mActivity;
private final ImageView mImageStatus;
private final TextView mImageDescr;
private final View mImageInformationContainer;
private final ImageView mBitmapSnapshot;
private final ImageButton mNextImage;
private final ImageButton mPreviousImage;
private final EditText mSolutionWords;
private final Spinner mSolutionLanguages;
private final Tongue[] mTongues;
private final ImageView mSolutionWordsStatus;
private final EditText mAuthorExtras;
private final ImageView mAuthorStatus;
private final EditText mAuthorName;
private final EditText mAuthorSource;
private final Button mAuthorApplyToOthers;
private int mBitmapsSelectCount;
private File mTempDirectory;
private String mBundleNameText;
private final List<SelectedBitmap> mSelectedBitmaps;
private int mDisplayedImageIndex;
private boolean mFillingImageData;
private BitmapUtil.ByteBufferHolder mBuffer;
private boolean mSaving;
private int mSaveResult;
private boolean mApplyToOthersStateRemoveConnection;
public BundleCreator(Activity activity) {
mSaveResult = ImageXmlWriter.RESULT_NONE;
mActivity = activity;
mBuffer = new BitmapUtil.ByteBufferHolder();
mSelectedBitmaps = new ArrayList<>();
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.workshop_bundle_creator, null);
//first obtain all relevant view references and setup listeners
mProgressText = (TextView) mView.findViewById(R.id.progress_descr);
mSave = (Button) mView.findViewById(R.id.bundle_save);
mSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSaveStarted();
}
});
mOriginStatus = (ImageView) mView.findViewById(R.id.origin_status);
mOriginName = (EditText) mView.findViewById(R.id.bundle_origin);
mOriginName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence originText, int start, int before, int count) {
boolean newOrigin = User.getInstance().saveOriginName(originText.toString());
if (newOrigin) {
applyOrigin();
}
updateStatus();
}
@Override
public void afterTextChanged(Editable s) {
}
});
mBundleNameStatus = (ImageView) mView.findViewById(R.id.bundle_name_status);
mBundleName = (EditText) mView.findViewById(R.id.bundle_name);
mBundleName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
String fileName = User.isValidFileName(text.toString());
if (fileName != null) {
mBundleNameText = fileName;
} else {
mBundleNameText = null;
}
updateStatus();
}
@Override
public void afterTextChanged(Editable s) {
}
});
mBitmapsDescr = (TextView) mView.findViewById(R.id.bitmaps_descr);
mBitmapsSelect = (Button) mView.findViewById(R.id.bitmap_open_selection);
mBitmapsSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openSelectBitmaps();
}
});
mBitmapsSelectProgress = (ProgressBar) mView.findViewById(R.id.bitmap_opening_progress);
mBitmapsSelectProgress.setVisibility(View.INVISIBLE);
mBitmapsSelectCount = 0;
mBitmapsSelectProgress.setIndeterminate(true);
mBitmapsStatus = (ImageView) mView.findViewById(R.id.bitmaps_status);
mImageInformationContainer = mView.findViewById(R.id.image_information_container);
mImageInformationContainer.setVisibility(View.INVISIBLE);
mImageDescr = (TextView) mView.findViewById(R.id.image_descr);
mImageStatus = (ImageView) mView.findViewById(R.id.image_status);
mBitmapSnapshot = (ImageView) mView.findViewById(R.id.bitmap_snapshot);
mNextImage = (ImageButton) mView.findViewById(R.id.next_image);
mNextImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSaving) {
return;
}
mDisplayedImageIndex++;
fillImageData();
updateStatus();
}
});
mPreviousImage = (ImageButton) mView.findViewById(R.id.previous_image);
mPreviousImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSaving) {
return;
}
mDisplayedImageIndex--;
fillImageData();
updateStatus();
}
});
mSolutionWordsStatus = (ImageView) mView.findViewById(R.id.solution_words_status);
mSolutionWords = (EditText) mView.findViewById(R.id.solution_words);
mSolutionWords.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!mFillingImageData) {
if (updateCurrentSolutionWords()) {
updateStatus();
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
mSolutionLanguages = (Spinner) mView.findViewById(R.id.solution_words_languages);
mTongues = Tongue.values();
mSolutionLanguages.setAdapter(new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_dropdown_item, mTongues));
mSolutionLanguages.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (!mFillingImageData) {
fillImageDataSolution();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
if (!mFillingImageData) {
fillImageDataSolution();
}
}
});
mAuthorStatus = (ImageView) mView.findViewById(R.id.author_status);
TextWatcher authorInfoListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!mFillingImageData) {
if (updateCurrentAuthorData()) {
updateStatus();
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
mAuthorName = (EditText) mView.findViewById(R.id.author_name);
mAuthorName.addTextChangedListener(authorInfoListener);
mAuthorSource = (EditText) mView.findViewById(R.id.author_source);
mAuthorSource.addTextChangedListener(authorInfoListener);
mAuthorExtras = (EditText) mView.findViewById(R.id.author_extras);
mAuthorExtras.addTextChangedListener(authorInfoListener);
mAuthorApplyToOthers = (Button) mView.findViewById(R.id.apply_author_info_to_others);
mAuthorApplyToOthers.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
applyAuthorInfoToOthers();
}
});
// wipe temp storage
mTempDirectory = User.clearTempDirectory();
// fill data into views
fillData();
//check for missing or illegal information and show this with status images
updateStatus();
}
private void onSaveStarted() {
if (mSaving) {
return;
}
mAuthorApplyToOthers.setEnabled(false);
mSave.setEnabled(false);
mBitmapsSelect.setEnabled(false);
mBundleName.setEnabled(false);
mAuthorExtras.setEnabled(false);
mAuthorName.setEnabled(false);
mAuthorSource.setEnabled(false);
mBundleName.setEnabled(false);
mOriginName.setEnabled(false);
mSolutionWords.setEnabled(false);
mSolutionLanguages.setEnabled(false);
final List<SelectedBitmap> toSave = new ArrayList<>(mSelectedBitmaps.size());
synchronized (mSelectedBitmaps) {
toSave.addAll(mSelectedBitmaps);
}
new AsyncTask<Void, Void, Void>() {
public String mmBundleName;
private String mmOrigin;
@Override
protected Void doInBackground(Void... params) {
mmOrigin = User.getInstance().getOriginName();
mmBundleName = mBundleNameText;
mSaveResult = ImageXmlWriter.writeBundle(mActivity, toSave, mmOrigin, mmBundleName);
Log.d("Image", "Write bundle, result: " + mSaveResult);
mSaving = false;
return null;
}
@Override
public void onPostExecute(Void nothing) {
if (mSaveResult == ImageXmlWriter.RESULT_SUCCESS) {
int estimatedSizeMB = getEstimatedSizeMB();
int count = mSelectedBitmaps.size();
synchronized (mSelectedBitmaps) {
mSelectedBitmaps.clear();
}
Toast.makeText(mActivity, mActivity.getResources().getString(R.string.bundle_creator_success, mmBundleName), Toast.LENGTH_LONG).show();
mBundleName.setText("");
BundleManager.onBundleCreated(mActivity, mmOrigin, mmBundleName, count, estimatedSizeMB, false, null);
} else if (mSaveResult == ImageXmlWriter.RESULT_TARGET_BUNDLE_EXISTS) {
Toast.makeText(mActivity, mActivity.getResources().getString(R.string.bundle_creator_failed_name_exists, mBundleNameText), Toast.LENGTH_LONG).show();
mBundleName.setText("");
} else if (mSaveResult == ImageXmlWriter.RESULT_EXTERNAL_STORAGE_PROBLEM || mSaveResult == ImageXmlWriter.RESULT_NO_TEMP_DIRECTORY) {
Toast.makeText(mActivity, R.string.bundle_creator_failed_external_storage, Toast.LENGTH_LONG).show();
}
reenable();
}
@Override
public void onCancelled(Void obj) {
reenable();
}
private void reenable() {
mSaving = false;
mBitmapsSelect.setEnabled(true);
mBundleName.setEnabled(true);
mAuthorApplyToOthers.setEnabled(true);
mAuthorExtras.setEnabled(true);
mAuthorName.setEnabled(true);
mAuthorSource.setEnabled(true);
mBundleName.setEnabled(true);
mOriginName.setEnabled(true);
mSolutionWords.setEnabled(true);
mSolutionLanguages.setEnabled(true);
fillData();
updateStatus();
}
}.execute();
}
private void applyAuthorInfoToOthers() {
mSaveResult = ImageXmlWriter.RESULT_NONE;
boolean updateStatus = false;
boolean updateApplyToOthers = false;
synchronized (mSelectedBitmaps) {
SelectedBitmap current = getCurrentBitmap();
if (current == null) {
return;
}
ImageAuthor author = current.mBuilder.getAuthor();
if (author == null) {
return; //should not be clickable if there is not an author for current
}
if (mApplyToOthersStateRemoveConnection) {
ImageAuthor copy = new ImageAuthor(author.getName(), author.getSource(), author.getLicense(), author.getTitle(), author.getExtras());
current.mBuilder.setAuthor(copy);
updateApplyToOthers = true;
} else {
for (SelectedBitmap curr : mSelectedBitmaps) {
if (curr.mBuilder.getAuthor() == null) {
curr.mBuilder.setAuthor(author);
updateApplyToOthers = true;
if (curr.checkedBuild()) {
updateStatus = true;
}
}
}
}
if (updateApplyToOthers) {
updateApplyToOthersButton(current);
}
}
if (updateStatus) {
updateStatus();
}
}
private Tongue getCurrentTongue() {
int selected = mSolutionLanguages.getSelectedItemPosition();
if (selected == AdapterView.INVALID_POSITION) {
for (selected = 0; selected < mTongues.length; selected++) {
if (mTongues[selected] == Language.getInstance().getTongue()) {
break;
}
}
}
if (selected < 0 || selected >= mTongues.length) {
return null;
}
return mTongues[selected];
}
private SelectedBitmap getCurrentBitmap() {
if (mDisplayedImageIndex < 0 || mDisplayedImageIndex >= mSelectedBitmaps.size()) {
return null;
}
SelectedBitmap selected;
synchronized (mSelectedBitmaps) {
selected = mSelectedBitmaps.get(mDisplayedImageIndex);
}
return selected;
}
private boolean updateCurrentSolutionWords() {
Tongue currentTongue = getCurrentTongue();
SelectedBitmap currentBitmap = getCurrentBitmap();
if (currentBitmap == null || currentTongue == null) {
return false;
}
String solutionWords = mSolutionWords.getText().toString();
List<Solution> solutions = currentBitmap.mBuilder.getSolutions();
int currentSolutionIndex;
Solution currentSolution = null;
if (solutions == null) {
currentSolutionIndex = -1;
} else {
for (currentSolutionIndex = 0; currentSolutionIndex < solutions.size(); currentSolutionIndex++) {
currentSolution = solutions.get(currentSolutionIndex);
if (currentSolution.getTongue().equals(currentTongue)) {
break;
}
currentSolution = null;
}
}
boolean updateStatus = false;
if (TextUtils.isEmpty(solutionWords)) {
if (currentSolution != null) {
solutions.remove(currentSolutionIndex);
if (solutions.isEmpty()) {
currentBitmap.mBuilder.setSolutions(null);
currentBitmap.mImage = null;
updateStatus = true;
}
}
} else {
Solution newSolution = Solution.makeSolution(currentTongue, solutionWords.split(","));
if (newSolution != null && solutions == null) {
solutions = new ArrayList<>(mTongues.length);
updateStatus = true;
} else if (solutions != null && currentSolution != null) {
solutions.remove(currentSolutionIndex);
}
if (newSolution != null) {
solutions.add(newSolution);
currentBitmap.mBuilder.setSolutions(solutions);
if (currentBitmap.checkedBuild()) {
updateStatus = true;
}
} else if (solutions != null && solutions.isEmpty()) {
currentBitmap.mBuilder.setSolutions(null);
currentBitmap.mImage = null;
updateStatus = true;
}
}
return updateStatus;
}
private boolean updateCurrentAuthorData() {
boolean updateStatus = false;
SelectedBitmap current = getCurrentBitmap();
if (current == null) {
return false;
}
mSaveResult = ImageXmlWriter.RESULT_NONE;
ImageAuthor author = current.mBuilder.getAuthor();
String authorNameText = mAuthorName.getText().toString();
if (author == null) {
author = new ImageAuthor();
current.mBuilder.setAuthor(author);
updateApplyToOthersButton(current);
updateStatus = true;
} else {
if (TextUtils.isEmpty(author.getName()) && !TextUtils.isEmpty(authorNameText)) {
updateStatus = true;
}
}
author.setName(authorNameText);
author.setSource(mAuthorSource.getText().toString());
author.setExtras(mAuthorExtras.getText().toString());
if (updateStatus) {
current.checkedBuild();
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
if (bitmap.mBuilder.getAuthor() == author) {
bitmap.checkedBuild();
}
}
}
}
if (TextUtils.isEmpty(author.getName())) {
boolean authorCleared = TextUtils.isEmpty(author.getSource()) && TextUtils.isEmpty(author.getExtras());
// if author data cleared, remove author from this and connected bitmaps
// in any case clear image as name is required
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
if (bitmap.mBuilder.getAuthor() == author) {
if (authorCleared) {
bitmap.mBuilder.setAuthor(null);
}
bitmap.mImage = null;
}
}
}
updateApplyToOthersButton(current);
updateStatus = true;
}
return updateStatus;
}
private void updateApplyToOthersButton(SelectedBitmap current) {
mAuthorApplyToOthers.setEnabled(false);
if (current == null) {
return;
}
mSaveResult = ImageXmlWriter.RESULT_NONE;
boolean allHaveAuthor = true;
boolean hasSharedAuthor = false;
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
if (bitmap.mBuilder.getAuthor() == null) {
allHaveAuthor = false;
break;
} else if (current != bitmap && bitmap.mBuilder.getAuthor().equals(current.mBuilder.getAuthor())) {
hasSharedAuthor = true;
}
}
}
mApplyToOthersStateRemoveConnection = allHaveAuthor && hasSharedAuthor;
mAuthorApplyToOthers.setEnabled(current.mBuilder.getAuthor() != null && (mApplyToOthersStateRemoveConnection || !allHaveAuthor));
mAuthorApplyToOthers.setText(mApplyToOthersStateRemoveConnection ? R.string.author_info_apply_to_others_remove_connection : R.string.author_info_apply_to_others);
}
private void openSelectBitmaps() {
Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT);
getIntent.setType("image/*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
getIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setType("image/*");
Intent chooserIntent = Intent.createChooser(getIntent, mActivity.getResources().getString(R.string.select_images_from_gallery));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{pickIntent});
mActivity.startActivityForResult(chooserIntent, WorkshopView.PICK_IMAGES_FOR_BUNDLE);
}
/**
* Checks and updates all status indicators, updates current image's status indicators.
* If all checks are passed, the bundle is allowed to save.
*/
private void updateStatus() {
String userOrigin = User.getInstance().getOriginName();
boolean originOk = userOrigin != null && userOrigin.equals(mOriginName.getText().toString().trim());
applyStatus(mOriginStatus, originOk);
boolean bundleNameOk = mBundleNameText != null && mBundleNameText.equals(mBundleName.getText().toString().trim());
applyStatus(mBundleNameStatus, bundleNameOk);
boolean bitmapsOk = mSelectedBitmaps.size() > 0 && mBitmapsSelectCount == 0;
applyStatus(mBitmapsStatus, bitmapsOk);
boolean imageInformationOk = getCompletedImageDataCount() == mSelectedBitmaps.size() && mSelectedBitmaps.size() > 0;
applyStatus(mImageStatus, imageInformationOk);
boolean allOk = originOk && bundleNameOk && bitmapsOk && imageInformationOk;
mSave.setEnabled(allOk);
if (allOk) {
if (mSaveResult == ImageXmlWriter.RESULT_SUCCESS) {
mProgressText.setText(R.string.bundle_creator_progress_saved);
} else if (mSaveResult == ImageXmlWriter.RESULT_NONE) {
mProgressText.setText(R.string.bundle_creator_progress_ready_to_save);
} else {
mProgressText.setText(mProgressText.getResources().getString(R.string.bundle_creator_progress_error_result, mSaveResult));
}
mSave.setCompoundDrawablesWithIntrinsicBounds(0, 0, ACCEPT_RESOURCE, 0);
} else {
mSaving = false;
mSave.setCompoundDrawablesWithIntrinsicBounds(0, 0, REFUSE_RESOURCE, 0);
mProgressText.setText(R.string.bundle_creator_progress_info_missing);
}
updateCurrentImageStatus();
fillImageHeaderData();
}
private void updateCurrentImageStatus() {
if (mImageInformationContainer.getVisibility() != View.VISIBLE) {
return;
}
SelectedBitmap current = getCurrentBitmap();
if (current == null) {
return;
}
boolean solutionWordsOk = current.mBuilder.getSolutions() != null && !current.mBuilder.getSolutions().isEmpty();
applyStatus(mSolutionWordsStatus, solutionWordsOk);
boolean authorOk = current.mBuilder.getAuthor() != null && !TextUtils.isEmpty(current.mBuilder.getAuthor().getName());
applyStatus(mAuthorStatus, authorOk);
}
private static void applyStatus(ImageView statusView, boolean accept) {
statusView.setImageResource(accept ? ACCEPT_RESOURCE : REFUSE_RESOURCE);
}
/**
* Fills the data currently available into the views. Fills selected bitmaps data.
*/
private void fillData() {
String origin = User.getInstance().getOriginName();
if (origin != null) {
mOriginName.setText(origin);
} else {
mOriginName.setText("");
}
if (mBundleNameText != null) {
mBundleName.setText(mBundleNameText);
} else {
mBundleName.setText("");
}
fillSelectedBitmapsData();
}
private int getEstimatedSizeMB() {
int sizeMB;
synchronized (mSelectedBitmaps) {
sizeMB = 0;
for (SelectedBitmap bitmap : mSelectedBitmaps) {
sizeMB += bitmap.mEstimatedSizeKB;
}
}
sizeMB /= 1024;
return sizeMB;
}
/**
* Fills selected bitmaps data, showing amount of loaded bitmaps
* and setting image data container visibility accordingly.
* Updates image data header.
*/
private void fillSelectedBitmapsData() {
int count = mSelectedBitmaps.size();
int sizeMB = getEstimatedSizeMB();
if (count > 0) {
fillImageData();
if (mImageInformationContainer.getVisibility() != View.VISIBLE) {
mImageInformationContainer.setVisibility(View.VISIBLE);
}
// would be strange to have 0MB even though there are images selected
sizeMB = Math.max(1, sizeMB);
} else {
mImageInformationContainer.setVisibility(View.GONE);
}
mBitmapsDescr.setText(mBitmapsDescr.getResources().getString(R.string.bundle_creator_bitmaps_descr, count, sizeMB));
mBitmapsSelect.setEnabled(true);
fillImageHeaderData();
}
/**
* Fills all image data of the currently selected bitmap if any.
*/
private void fillImageData() {
boolean wasFilling = mFillingImageData;
mFillingImageData = true;
mDisplayedImageIndex = Math.max(mDisplayedImageIndex, 0);
mDisplayedImageIndex = Math.min(mDisplayedImageIndex, mSelectedBitmaps.size() - 1);
if (mDisplayedImageIndex < 0) {
return;
}
final SelectedBitmap selected = getCurrentBitmap();
if (selected == null) {
return;
}
mNextImage.setVisibility(mDisplayedImageIndex < mSelectedBitmaps.size() - 1 ? View.VISIBLE : View.INVISIBLE);
mPreviousImage.setVisibility(mDisplayedImageIndex > 0 ? View.VISIBLE : View.INVISIBLE);
mSaveResult = ImageXmlWriter.RESULT_NONE;
if (selected.mSnapshot != null) {
mBitmapSnapshot.setImageBitmap(selected.mSnapshot);
selected.mLoadSnapshotTask = null;
} else {
mBitmapSnapshot.setImageResource(REFUSE_RESOURCE);
selected.mLoadSnapshotTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
ImageUtil.CACHE.makeReusable(selected.mSnapshot);
selected.mSnapshot = ImageUtil.loadBitmap(selected.mPathInTemp, SNAPSHOT_WIDTH, SNAPSHOT_HEIGHT, BitmapUtil.MODE_FIT_INSIDE);
return null;
}
@Override
public void onPostExecute(Void result) {
fillImageData();
}
}.execute();
}
fillImageDataSolution();
fillImageDataAuthor();
mFillingImageData = wasFilling;
updateCurrentImageStatus();
}
private void fillImageDataSolution() {
boolean wasFilling = mFillingImageData;
mFillingImageData = true;
Tongue currentTongue = getCurrentTongue();
if (currentTongue == null || mDisplayedImageIndex < 0) {
return;
}
SelectedBitmap bitmap = getCurrentBitmap();
if (bitmap == null) {
return;
}
List<Solution> solutions = bitmap.mBuilder.getSolutions();
mSolutionWords.setText("");
if (solutions != null) {
Solution curr = null;
for (Solution sol : solutions) {
if (sol.getTongue().equals(currentTongue)) {
curr = sol;
break;
}
}
if (curr != null) {
StringBuilder words = new StringBuilder();
List<String> wordsList = curr.getWords();
for (int i = 0; i < wordsList.size(); i++) {
words.append(wordsList.get(i));
if (i < wordsList.size() - 1) {
words.append(",");
}
}
mSolutionWords.setText(words.toString());
}
}
mFillingImageData = wasFilling;
}
private void fillImageDataAuthor() {
boolean wasFilling = mFillingImageData;
mFillingImageData = true;
SelectedBitmap current = getCurrentBitmap();
mAuthorName.setText("");
mAuthorSource.setText("");
mAuthorExtras.setText("");
if (current != null && current.mBuilder.getAuthor() != null) {
ImageAuthor author = current.mBuilder.getAuthor();
mAuthorName.setText(author.getName());
mAuthorSource.setText(author.getSource());
mAuthorExtras.setText(author.getExtras());
}
updateApplyToOthersButton(current);
mFillingImageData = wasFilling;
}
private int getCompletedImageDataCount() {
int count = 0;
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
if (bitmap.mImage != null) {
count++;
}
}
}
return count;
}
private void fillImageHeaderData() {
int completedCount = getCompletedImageDataCount();
mImageDescr.setText(mImageDescr.getResources().getString(R.string.bundle_creator_image_descr, completedCount, mSelectedBitmaps.size()));
}
/**
* Required for reacting to selected bitmaps from galleries. Loads and prepares
* bitmaps asynchronously, showing progress on the fly and an indeterminate progress bar.
* @param requestCode The request code of the activity result started for intent.
* @param resultCode The result code. Does nothing if not ok.
* @param data The returned data.
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK || requestCode != WorkshopView.PICK_IMAGES_FOR_BUNDLE || data == null) {
return;
}
ClipData clipData = VersionSafe.getClipData(data);
Uri[] selectedImageUris;
if (clipData == null) {
selectedImageUris = new Uri[] {data.getData()};
} else {
selectedImageUris = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item curr = clipData.getItemAt(i);
selectedImageUris[i] = curr.getUri();
}
}
new AsyncTask<Uri, SelectedBitmap, Void>() {
@Override
public void onPreExecute() {
ensureTempDirectory();
mBitmapsSelectCount++;
if (mBitmapsSelectCount > 0) {
mBitmapsSelectProgress.setVisibility(View.VISIBLE);
}
}
@Override
protected Void doInBackground(Uri... params) {
String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.SIZE, MediaStore.Images.Media.DISPLAY_NAME };
for (Uri selectedImages : params) {
if (selectedImages == null) {
continue;
}
Cursor cursor = null;
try {
cursor = mActivity.getContentResolver().query(selectedImages,
columns, null, null, null);
if (cursor == null || cursor.isAfterLast()) {
continue; // empty cursor
}
cursor.moveToFirst();
int pathIndex = cursor.getColumnIndexOrThrow(columns[0]);
int sizeIndex = cursor.getColumnIndexOrThrow(columns[1]);
int nameIndex = cursor.getColumnIndex(columns[2]);
while (!cursor.isAfterLast()) {
String picturePath = cursor.getString(pathIndex);
int sizeKB = cursor.getInt(sizeIndex) / (1024);
if (picturePath == null) {
InputStream input = null;
try {
input = mActivity.getContentResolver().openInputStream(selectedImages);
} catch (FileNotFoundException e1) {
Log.e("HomeStuff", "File not found when trying to decode bitmap from stream: " + e1);
}
String bitmapName = null;
if (nameIndex != -1) {
bitmapName = cursor.getString(nameIndex);
}
if (TextUtils.isEmpty(bitmapName)) {
bitmapName = String.valueOf(System.currentTimeMillis());
}
File bitmapFileInTemp = new File(mTempDirectory, bitmapName);
Bitmap bitmap = ImageUtil.loadBitmap(input, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT, BitmapUtil.MODE_FIT_NO_GROW);
if (ImageUtil.saveToFile(bitmap, bitmapFileInTemp, null, COMPRESSION)) {
publishProgress(new SelectedBitmap(bitmap, bitmapFileInTemp, sizeKB, mBuffer));
}
} else {
File path = new File(picturePath);
String bitmapName = path.getName();
if (TextUtils.isEmpty(bitmapName) && nameIndex != -1) {
bitmapName = cursor.getString(nameIndex);
}
File targetPath = new File(mTempDirectory, bitmapName);
Bitmap bitmap = ImageUtil.loadBitmap(path, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT, BitmapUtil.MODE_FIT_NO_GROW);
if (ImageUtil.saveToFile(bitmap, targetPath, null, COMPRESSION)) {
publishProgress(new SelectedBitmap(bitmap, targetPath, sizeKB, mBuffer));
}
}
cursor.moveToNext();
}
} catch (Exception e) {
Log.e("HomeStuff", "Error retrieving images from cursor " + cursor + ": " + e);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
return null;
}
@Override
public void onProgressUpdate(SelectedBitmap... bitmap) {
if (!isImageSelected(bitmap[0].mPathInTemp.getAbsolutePath())) {
synchronized (mSelectedBitmaps) {
mSelectedBitmaps.add(bitmap[0]);
}
}
mSaveResult = ImageXmlWriter.RESULT_NONE;
fillSelectedBitmapsData();
updateStatus();
}
@Override
public void onPostExecute(Void nothing) {
mSaveResult = ImageXmlWriter.RESULT_NONE;
Log.d("HomeStuff", "Finished loading bitmaps (Processes still loading: " + (mBitmapsSelectCount - 1) + ").");
mBitmapsSelectCount--;
if (mBitmapsSelectCount <= 0) {
mBitmapsSelectProgress.setVisibility(View.INVISIBLE);
}
applyOrigin();
fillSelectedBitmapsData();
updateStatus();
}
}.execute(selectedImageUris);
}
/**
* Applies origin to all selected bitmaps.
*/
private void applyOrigin() {
String origin = User.getInstance().getOriginName();
if (origin == null) {
return;
}
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
bitmap.mBuilder.setOrigin(origin);
}
}
}
private boolean isImageSelected(String pathInTemp) {
synchronized (mSelectedBitmaps) {
for (SelectedBitmap bitmap : mSelectedBitmaps) {
if (bitmap.mPathInTemp.getAbsolutePath().equalsIgnoreCase(pathInTemp)) {
return true;
}
}
}
return false;
}
private synchronized void ensureTempDirectory() {
if (mTempDirectory == null) {
mTempDirectory = User.getTempDirectory();
if (mTempDirectory == null) {
throw new IllegalStateException("No temp directory available");
}
}
}
public static class SelectedBitmap {
public final File mPathInTemp;
private Image.Builder mBuilder;
public Image mImage;
private int mEstimatedSizeKB;
private AsyncTask<Void, Void, Void> mLoadSnapshotTask;
public Bitmap mSnapshot;
public SelectedBitmap(Bitmap bitmap, File imagePath, int sizeKB, BitmapUtil.ByteBufferHolder buffer) {
mBuilder = new Image.Builder();
mBuilder.calculateHashAndPreferences(buffer, bitmap);
mBuilder.setRelativeImagePath(User.extractRelativePathInsideTempDirectory(imagePath));
mPathInTemp = imagePath;
mEstimatedSizeKB = sizeKB;
}
@Override
public String toString() {
return mImage != null ? mImage.toString() : "Unprepared SelectedBitmap: " + mPathInTemp + " (" + mEstimatedSizeKB + ")";
}
public boolean checkedBuild() {
if (mBuilder.getOrigin() != null && !mBuilder.getOrigin().equals(Image.ORIGIN_IS_THE_APP)
&& mBuilder.getSolutions() != null && !mBuilder.getSolutions().isEmpty()
&& mBuilder.getAuthor() != null && !TextUtils.isEmpty(mBuilder.getAuthor().getName()) ) {
try {
mImage = mBuilder.build();
} catch (BuildException be) {
Log.e("Image", "Failed building image: " + be);
mImage = null;
}
return true;
}
return false;
}
}
public View getView() {
return mView;
}
}