/*
* 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.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import dan.dit.whatsthat.R;
import dan.dit.whatsthat.preferences.User;
import dan.dit.whatsthat.system.ImageDataDownload;
import dan.dit.whatsthat.testsubject.TestSubject;
import dan.dit.whatsthat.testsubject.shopping.sortiment.ShopArticleDownload;
import dan.dit.whatsthat.util.general.BuildException;
import dan.dit.whatsthat.util.image.ExternalStorage;
/**
* Created by daniel on 09.09.15.
*/
public class BundleManager {
public static final String BUNDLES_DIRECTORY_NAME = ".bundles";
public static final String BUNDLE_EXTENSION = ".wtb";
private static final String BUNDLE_PREFERENCES = "dan.dit.whatsthat.bundle_preferences";
private static final String PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY = "dan.dit.whatsthat.origin_and_names";
private final Activity mActivity;
private final BundlesAdapter mAdapter;
private static File sBundlesDir;
private ExpandableListView mView;
private SortedSet<ImageBundle> mBundles;
public BundleManager(Activity activity) {
mActivity = activity;
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = (ExpandableListView) inflater.inflate(R.layout.workshop_bundle_manager, null);
sBundlesDir = ensureBundleDirectory();
mAdapter = new BundlesAdapter(activity);
loadBundles();
}
public void refresh() {
loadBundles();
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
public static File ensureBundleDirectory() {
if (sBundlesDir != null) {
return sBundlesDir;
}
boolean exists = false;
File bundlesDir = null;
String path = ExternalStorage.getExternalStoragePathIfMounted(BUNDLES_DIRECTORY_NAME);
if (path != null) {
bundlesDir = new File(path);
if (bundlesDir.mkdirs() || bundlesDir.isDirectory()) {
exists = true;
sBundlesDir = bundlesDir;
}
}
return exists ? bundlesDir : null;
}
public static File makeBundleFile(File dir, String bundleOrigin, String bundleName) {
return new File(dir, bundleOrigin + "_" + bundleName + BundleManager.BUNDLE_EXTENSION);
}
public static String[] getOriginAndNameOfBundleFile(File file) {
if (file == null) {
return null;
}
String name = file.getName();
if (!name.endsWith(BUNDLE_EXTENSION)) {
return null;
}
return name.substring(0, name.length() - BUNDLE_EXTENSION.length()).split("_");
}
public View getView() {
return mView;
}
public static void onBundleCreated(Context context, String origin, String bundleName, int count, int sizeMB, boolean synced, String downloadArticleKey) {
ImageBundle created = null;
try {
created = new ImageBundle(ensureBundleDirectory(), origin, bundleName, count, sizeMB, synced, downloadArticleKey);
} catch(BuildException e) {
Log.e("Image", "Failed building image bundle onBundleCreated: " + e);
}
if (created != null) {
created.saveBundle(context);
}
}
private void loadBundles() {
ensureBundleDirectory();
if (sBundlesDir == null) {
return;
}
mBundles = new TreeSet<>();
SharedPreferences prefs = mActivity.getSharedPreferences(BUNDLE_PREFERENCES, Context.MODE_PRIVATE);
Set<String> originAndNames = prefs.getStringSet(PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY, new HashSet<String>());
for (String originAndName : originAndNames) {
String[] split = originAndName.split("_");
if (split.length >= 2) {
ImageBundle curr = null;
try {
curr = new ImageBundle(sBundlesDir, split[0], split[1],
prefs.getInt(originAndName + "_count", -1),
prefs.getInt(originAndName + "_sizeMB", -1),
prefs.getBoolean(originAndName + "_synced", false),
prefs.getString(originAndName + "_download", null));
} catch (BuildException e) {
Log.e("Image", "Error building image bundle in manager when loading bundles." + e);
}
if (curr != null) {
mBundles.add(curr);
}
}
}
// next check files in bundles directory, maybe some bundle appeared there
for (File file : sBundlesDir.listFiles()) {
if (file.getName().endsWith(BUNDLE_EXTENSION) && !file.isDirectory()) {
ImageBundle curr = null;
try {
curr = new ImageBundle(file);
} catch (BuildException be) {
Log.e("Image", "Error building image bundle from file " + file + ": " + be);
}
if (curr != null) {
mBundles.add(curr);
}
}
}
if (mBundles != null) {
mView.setAdapter(mAdapter);
}
}
private static class ImageBundle implements Comparable<ImageBundle> {
private final String mOrigin;
private final String mName;
private final File mPath;
private String mDownloadArticleKey;
private int mEstimatedSizeMB;
private int mEstimatedImagesCount;
private boolean mSynced;
public ImageBundle(File bundlesDir, String origin, String name, int count, int sizeMB, boolean synced, String downloadArticleKey) throws BuildException {
mOrigin = origin;
mName = name;
mEstimatedImagesCount = count;
mEstimatedSizeMB = sizeMB;
mPath = makeBundleFile(bundlesDir, origin, name);
mDownloadArticleKey = downloadArticleKey;
mSynced = synced;
checkOriginAndName();
}
public ImageBundle setSynced(Context context) {
if (!mSynced) {
mSynced = true;
saveBundle(context);
}
return this;
}
public ImageBundle(File file) throws BuildException {
mPath = file;
String[] originAndName = getOriginAndNameOfBundleFile(mPath);
if (originAndName == null || originAndName.length < 2) {
throw new BuildException("No valid bundle file given.");
}
mOrigin = originAndName[0];
mName = originAndName[1];
mEstimatedImagesCount = -1;
mEstimatedSizeMB = -1;
checkOriginAndName();
}
public void saveBundle(Context context) {
SharedPreferences prefs = context.getSharedPreferences(BUNDLE_PREFERENCES, Context.MODE_PRIVATE);
String key = mOrigin + "_" + mName;
Set<String> allOriginNames = prefs.getStringSet(PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY, new HashSet<String>());
allOriginNames.add(key);
SharedPreferences.Editor editor = prefs.edit().putInt(key + "_count", mEstimatedImagesCount)
.putInt(key + "_sizeMB", mEstimatedSizeMB)
.putBoolean(key + "_synced", mSynced)
.putStringSet(PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY, allOriginNames);
if (!TextUtils.isEmpty(mDownloadArticleKey)) {
editor.putString(key + "_download", mDownloadArticleKey);
} else {
editor.remove(key + "_download");
}
editor.apply();
}
public void removeBundle(Context context) {
SharedPreferences prefs = context.getSharedPreferences(BUNDLE_PREFERENCES, Context.MODE_PRIVATE);
String key = mOrigin + "_" + mName;
Set<String> allOriginNames = prefs.getStringSet(PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY, new HashSet<String>());
allOriginNames.remove(key);
prefs.edit().remove(key + "_count")
.remove(key + "_sizeMB")
.remove(key + "_synced")
.putStringSet(PREFERENCE_ALL_ORIGIN_AND_NAMES_KEY, allOriginNames)
.remove(key + "_download").apply();
}
private void checkOriginAndName() throws BuildException {
if (TextUtils.isEmpty(mOrigin)) {
throw new BuildException().setMissingData("ImageBundle", "Origin");
}
if (TextUtils.isEmpty(mName)) {
throw new BuildException().setMissingData("ImageBundle", "Name");
}
}
@Override
public boolean equals(Object other) {
return other == this || (other instanceof ImageBundle
&& mName.equals(((ImageBundle) other).mName) && mOrigin.equals(((ImageBundle) other).mOrigin));
}
@Override
public int hashCode() {
return mName.hashCode() + 32 * mOrigin.hashCode();
}
@Override
public int compareTo(@NonNull ImageBundle another) {
int originDiff = mOrigin.compareToIgnoreCase(another.mOrigin);
if (originDiff == 0) {
return mName.compareToIgnoreCase(another.mName);
}
return originDiff;
}
}
private class BundlesAdapter extends BaseExpandableListAdapter {
private final LayoutInflater mInflater;
private Context mContext;
public BundlesAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getGroupCount() {
int count = 0;
String lastOrigin = null;
for (ImageBundle bundle : mBundles) {
if (lastOrigin == null || !lastOrigin.equalsIgnoreCase(bundle.mOrigin)) {
lastOrigin = bundle.mOrigin;
count++;
}
}
return count;
}
@Override
public int getChildrenCount(int i) {
int count = 0;
int currOriginIndex = -1;
String lastOrigin = null;
for (ImageBundle bundle : mBundles) {
if (lastOrigin == null || !lastOrigin.equalsIgnoreCase(bundle.mOrigin)) {
lastOrigin = bundle.mOrigin;
currOriginIndex++;
if (currOriginIndex > i) {
break;
}
}
if (currOriginIndex == i) {
count++;
}
}
return count;
}
@Override
public Object getGroup(int i) {
SortedSet<ImageBundle> group = new TreeSet<>();
int currOriginIndex = -1;
String lastOrigin = null;
for (ImageBundle bundle : mBundles) {
if (lastOrigin == null || !lastOrigin.equalsIgnoreCase(bundle.mOrigin)) {
lastOrigin = bundle.mOrigin;
currOriginIndex++;
if (currOriginIndex > i) {
break;
}
}
if (currOriginIndex == i) {
group.add(bundle);
}
}
return group;
}
@Override
public Object getChild(int i, int i1) {
int currOriginIndex = -1;
String lastOrigin = null;
int currChildIndex = -1;
for (ImageBundle bundle : mBundles) {
if (lastOrigin == null || !lastOrigin.equalsIgnoreCase(bundle.mOrigin)) {
lastOrigin = bundle.mOrigin;
currOriginIndex++;
if (currOriginIndex > i) {
break;
}
}
if (currOriginIndex == i) {
currChildIndex++;
if (currChildIndex == i1) {
return bundle;
}
}
}
return null;
}
@Override
public long getGroupId(int i) {
return i + 1;
}
@Override
public long getChildId(int i, int i1) {
return i1 + 1;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.workshop_bundle_manager_category, null);
}
SortedSet<ImageBundle> group = (SortedSet<ImageBundle>) getGroup(groupPosition);
int imageCount = 0;
int sizeMB = 0;
for (ImageBundle bundle : group) {
if (bundle.mEstimatedImagesCount > 0) {
imageCount += bundle.mEstimatedImagesCount;
}
if (bundle.mEstimatedSizeMB > 0) {
sizeMB += bundle.mEstimatedSizeMB;
}
}
sizeMB = Math.max(1, sizeMB);
TextView imageCountView = (TextView) convertView.findViewById(R.id.image_count);
if (imageCount > 0) {
imageCountView.setText(imageCountView.getResources().getQuantityString(R.plurals.images_in_bundle, imageCount, imageCount));
imageCountView.setVisibility(View.VISIBLE);
} else {
imageCountView.setVisibility(View.INVISIBLE);
}
TextView sizeMBView = (TextView) convertView.findViewById(R.id.image_size_mb);
sizeMBView.setText("~" + String.valueOf(sizeMB) + "MB");
if (!group.isEmpty()) {
((TextView) convertView.findViewById(R.id.bundle_origin)).setText(group.first().mOrigin);
} else {
convertView.findViewById(R.id.bundle_origin).setVisibility(View.INVISIBLE); // shouldn't be the case
}
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.workshop_bundle_manager_bundle, null);
}
final ImageBundle bundle = (ImageBundle) getChild(groupPosition, childPosition);
((TextView) convertView.findViewById(R.id.bundle_name)).setText(bundle.mName);
TextView imageCountView = (TextView) convertView.findViewById(R.id.image_count);
if (bundle.mEstimatedImagesCount > 0) {
imageCountView.setText(imageCountView.getResources().getQuantityString(R.plurals.images_in_bundle, bundle.mEstimatedImagesCount, bundle.mEstimatedImagesCount));
imageCountView.setVisibility(View.VISIBLE);
} else {
imageCountView.setVisibility(View.INVISIBLE);
}
TextView sizeMBView = (TextView) convertView.findViewById(R.id.image_size_mb);
sizeMBView.setText("~" + String.valueOf(Math.max(1, bundle.mEstimatedSizeMB)) + "MB");
ImageButton download = (ImageButton) convertView.findViewById(R.id.download);
if (TextUtils.isEmpty(bundle.mDownloadArticleKey)) {
download.setVisibility(View.GONE);
} else {
final ShopArticleDownload downloadArticle = (ShopArticleDownload) TestSubject.getInstance().getShopSortiment().getArticle(bundle.mDownloadArticleKey);
if (downloadArticle == null) {
download.setVisibility(View.GONE);
} else {
download.setVisibility(View.VISIBLE);
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setEnabled(false);
downloadArticle.start();
}
});
}
}
final ImageButton syncUnsync = (ImageButton) convertView.findViewById(R.id.sync_unsync);
if (bundle.mPath != null && bundle.mPath.exists()) {
// currently unsync option not available, and sync only if permission is granted
if (User.getInstance().hasPermission(User.PERMISSION_BUNDLE_SYNC_ALLOWED)) {
syncUnsync.setVisibility(View.VISIBLE);
syncUnsync.setEnabled(true);
syncUnsync.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setEnabled(false);
ImageDataDownload includer = new ImageDataDownload(mContext, bundle.mOrigin, bundle.mName, bundle.mEstimatedSizeMB, null, new ImageDataDownload.Feedback() {
@Override
public void setIsWorking(boolean isWorking) {
}
@Override
public void onError(int messageResId, int errorCode) {
Toast.makeText(mContext, mContext.getResources().getString(messageResId, errorCode), Toast.LENGTH_SHORT).show();
syncUnsync.setEnabled(true);
}
@Override
public void onDownloadComplete() {
}
@Override
public void onComplete() {
Toast.makeText(mContext, R.string.download_article_toast_complete, Toast.LENGTH_SHORT).show();
bundle.setSynced(mContext);
mAdapter.notifyDataSetChanged();
}
@Override
public void onProgressUpdate(int progress) {
}
});
if (!bundle.mOrigin.equalsIgnoreCase(Image.ORIGIN_IS_THE_APP)) {
includer.setKeepBundleAfterSync();
}
includer.start();
}
});
} else {
syncUnsync.setVisibility(View.GONE);
}
} else {
syncUnsync.setVisibility(View.GONE);
}
ImageButton share = (ImageButton) convertView.findViewById(R.id.share);
if (bundle.mPath != null && bundle.mPath.exists()) {
share.setVisibility(View.VISIBLE);
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(bundle.mPath));
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(mContext, R.string.share_failed, Toast.LENGTH_SHORT).show();
}
}
});
} else {
share.setVisibility(View.GONE);
}
ImageButton delete = (ImageButton) convertView.findViewById(R.id.delete);
if (bundle.mPath != null && bundle.mPath.exists()) {
delete.setVisibility(View.VISIBLE);
// currently only deleting of bundle file possible, not deleting images from images directory (and therefore unsyncing from database)
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (bundle.mPath.delete()) {
if (!bundle.mSynced) {
bundle.removeBundle(mContext);
}
refresh();
}
}
});
} else {
delete.setVisibility(View.GONE);
}
return convertView;
}
@Override
public boolean isChildSelectable(int i, int childPosition) {
return false;
}
}
}