diff --git a/build.gradle b/build.gradle index 7340820c09..464083acbf 100644 --- a/build.gradle +++ b/build.gradle @@ -224,10 +224,6 @@ android { proguardFiles = buildTypes.debug.proguardFiles signingConfig signingConfigs.release } - dev.initWith(buildTypes.debug) - dev { - buildConfigField "boolean", "DEV_BUILD", "true" - } } sourceSets { diff --git a/res/layout/attachment_type_selector.xml b/res/layout/attachment_type_selector.xml index 58723e9960..46d2526e21 100644 --- a/res/layout/attachment_type_selector.xml +++ b/res/layout/attachment_type_selector.xml @@ -5,12 +5,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attachment_type_selector_background" - android:elevation="4dp" - android:padding="16dp"> + android:elevation="4dp"> + + + + + \ No newline at end of file diff --git a/res/layout/recent_photo_view_item.xml b/res/layout/recent_photo_view_item.xml new file mode 100644 index 0000000000..39214dfc50 --- /dev/null +++ b/res/layout/recent_photo_view_item.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 4cb4cb6aa9..b88153e552 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -179,4 +179,8 @@ + + + + diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 90664e440f..6b1f331698 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -985,7 +985,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeBubble.getBackground().setColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY); colors.recycle(); - attachmentTypeSelector = new AttachmentTypeSelector(this, new AttachmentTypeListener()); + attachmentTypeSelector = new AttachmentTypeSelector(this, getSupportLoaderManager(), new AttachmentTypeListener()); attachmentManager = new AttachmentManager(this, this); audioRecorder = new AudioRecorder(this, masterSecret); @@ -1607,6 +1607,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public void onClick(int type) { addAttachment(type); } + + @Override + public void onQuickAttachment(Uri uri) { + Intent intent = new Intent(); + intent.setData(uri); + + onActivityResult(PICK_IMAGE, RESULT_OK, intent); + } } private class QuickCameraToggleListener implements OnClickListener { diff --git a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java index 1d6c017c9c..cfb524b9de 100644 --- a/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ b/src/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java @@ -5,9 +5,11 @@ import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.LoaderManager; import android.util.Pair; import android.view.Gravity; import android.view.LayoutInflater; @@ -52,11 +54,12 @@ public class AttachmentTypeSelector extends PopupWindow { private @Nullable View currentAnchor; private @Nullable AttachmentClickedListener listener; - public AttachmentTypeSelector(@NonNull Context context, @Nullable AttachmentClickedListener listener) { + public AttachmentTypeSelector(@NonNull Context context, @NonNull LoaderManager loaderManager, @Nullable AttachmentClickedListener listener) { super(context); - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.attachment_type_selector, null, true); + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.attachment_type_selector, null, true); + RecentPhotoViewRail recentPhotos = ViewUtil.findById(layout, R.id.recent_photos); this.listener = listener; this.imageButton = ViewUtil.findById(layout, R.id.gallery_button); @@ -76,6 +79,7 @@ public class AttachmentTypeSelector extends PopupWindow { this.locationButton.setOnClickListener(new PropagatingClickListener(ADD_LOCATION)); this.gifButton.setOnClickListener(new PropagatingClickListener(ADD_GIF)); this.closeButton.setOnClickListener(new CloseClickListener()); + recentPhotos.setListener(new RecentPhotoSelectedListener()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { ViewUtil.findById(layout, R.id.location_linear_layout).setVisibility(View.INVISIBLE); @@ -89,6 +93,8 @@ public class AttachmentTypeSelector extends PopupWindow { setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); setFocusable(true); setTouchable(true); + + loaderManager.initLoader(1, null, recentPhotos); } public void show(@NonNull Activity activity, final @NonNull View anchor) { @@ -236,6 +242,15 @@ public class AttachmentTypeSelector extends PopupWindow { return new Pair<>(x, y); } + private class RecentPhotoSelectedListener implements RecentPhotoViewRail.OnItemClickedListener { + @Override + public void onItemClicked(Uri uri) { + animateWindowOutTranslate(getContentView()); + + if (listener != null) listener.onQuickAttachment(uri); + } + } + private class PropagatingClickListener implements View.OnClickListener { private final int type; @@ -262,6 +277,7 @@ public class AttachmentTypeSelector extends PopupWindow { public interface AttachmentClickedListener { public void onClick(int type); + public void onQuickAttachment(Uri uri); } } diff --git a/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java new file mode 100644 index 0000000000..71617436fe --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java @@ -0,0 +1,146 @@ +package org.thoughtcrime.securesms.components; + + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.signature.MediaStoreSignature; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; +import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks { + + @NonNull private final RecyclerView recyclerView; + @Nullable private OnItemClickedListener listener; + + public RecentPhotoViewRail(Context context) { + this(context, null); + } + + public RecentPhotoViewRail(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RecentPhotoViewRail(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + inflate(context, R.layout.recent_photo_view, this); + + this.recyclerView = ViewUtil.findById(this, R.id.photo_list); + this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); + this.recyclerView.setItemAnimator(new DefaultItemAnimator()); + } + + public void setListener(@Nullable OnItemClickedListener listener) { + this.listener = listener; + + if (this.recyclerView.getAdapter() != null) { + ((RecentPhotoAdapter)this.recyclerView.getAdapter()).setListener(listener); + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new RecentPhotosLoader(getContext()); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + this.recyclerView.setAdapter(new RecentPhotoAdapter(getContext(), data, RecentPhotosLoader.BASE_URL, listener)); + } + + @Override + public void onLoaderReset(Loader loader) { + ((CursorRecyclerViewAdapter)this.recyclerView.getAdapter()).changeCursor(null); + } + + private static class RecentPhotoAdapter extends CursorRecyclerViewAdapter { + + @NonNull private final Uri baseUri; + @Nullable private OnItemClickedListener clickedListener; + + private RecentPhotoAdapter(@NonNull Context context, @NonNull Cursor cursor, @NonNull Uri baseUri, @Nullable OnItemClickedListener listener) { + super(context, cursor); + this.baseUri = baseUri; + this.clickedListener = listener; + } + + @Override + public RecentPhotoViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.recent_photo_view_item, parent, false); + + return new RecentPhotoViewHolder(itemView); + } + + @Override + public void onBindItemViewHolder(RecentPhotoViewHolder viewHolder, @NonNull Cursor cursor) { + viewHolder.imageView.setImageDrawable(null); + + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)); + long dateTaken = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN)); + long dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED)); + String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE)); + int orientation = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION)); + + final Uri uri = Uri.withAppendedPath(baseUri, Long.toString(id)); + + Key signature = new MediaStoreSignature(mimeType, dateModified, orientation); + + Glide.with(getContext()) + .fromMediaStore() + .load(uri) + .signature(signature) + .centerCrop() + .into(viewHolder.imageView); + + viewHolder.imageView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (clickedListener != null) clickedListener.onItemClicked(uri); + } + }); + + } + + public void setListener(@Nullable OnItemClickedListener listener) { + this.clickedListener = listener; + } + + static class RecentPhotoViewHolder extends RecyclerView.ViewHolder { + + ImageView imageView; + + RecentPhotoViewHolder(View itemView) { + super(itemView); + + this.imageView = ViewUtil.findById(itemView, R.id.thumbnail); + } + } + } + + public interface OnItemClickedListener { + public void onItemClicked(Uri uri); + } +} diff --git a/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java index b4028febf1..5ee8903589 100644 --- a/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java +++ b/src/org/thoughtcrime/securesms/components/SquareFrameLayout.java @@ -2,34 +2,45 @@ package org.thoughtcrime.securesms.components; import android.annotation.TargetApi; import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.BitmapFactory; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.widget.FrameLayout; +import org.thoughtcrime.securesms.R; + public class SquareFrameLayout extends FrameLayout { + + private final boolean squareHeight; + @SuppressWarnings("unused") public SquareFrameLayout(Context context) { - super(context); + this(context, null); } @SuppressWarnings("unused") public SquareFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } @TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused") public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - } - @TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("unused") - public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + if (attrs != null) { + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SquareFrameLayout, 0, 0); + this.squareHeight = typedArray.getBoolean(R.styleable.SquareFrameLayout_square_height, false); + typedArray.recycle(); + } else { + this.squareHeight = false; + } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //noinspection SuspiciousNameCombination - super.onMeasure(widthMeasureSpec, widthMeasureSpec); + if (squareHeight) super.onMeasure(heightMeasureSpec, heightMeasureSpec); + else super.onMeasure(widthMeasureSpec, widthMeasureSpec); } } diff --git a/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java new file mode 100644 index 0000000000..4a649572f9 --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/loaders/RecentPhotosLoader.java @@ -0,0 +1,39 @@ +package org.thoughtcrime.securesms.database.loaders; + + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.support.v4.content.CursorLoader; + +import org.thoughtcrime.securesms.database.DatabaseFactory; + +public class RecentPhotosLoader extends CursorLoader { + + public static Uri BASE_URL = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + private static final String[] PROJECTION = new String[] { + MediaStore.Images.ImageColumns._ID, + MediaStore.Images.ImageColumns.DATE_TAKEN, + MediaStore.Images.ImageColumns.DATE_MODIFIED, + MediaStore.Images.ImageColumns.ORIENTATION, + MediaStore.Images.ImageColumns.MIME_TYPE + }; + + private final Context context; + + public RecentPhotosLoader(Context context) { + super(context); + this.context = context.getApplicationContext(); + } + + @Override + public Cursor loadInBackground() { + return context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + PROJECTION, null, null, + MediaStore.Images.ImageColumns.DATE_MODIFIED + " DESC"); + } + + +}