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");
+ }
+
+
+}