Refactor media overview activity to display documents
// FREEBIE
This commit is contained in:
parent
c6b2e785a5
commit
8ce914a344
19 changed files with 504 additions and 155 deletions
|
@ -314,6 +314,7 @@
|
|||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MediaOverviewActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:background="?attr/media_overview_toolbar_background"
|
||||
android:titleTextColor="?attr/media_overview_toolbar_foreground"
|
||||
android:foreground="?attr/media_overview_toolbar_foreground"
|
||||
app:layout_scrollFlags="scroll|enterAlways"/>
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
app:tabBackground="?attr/media_overview_toolbar_background"
|
||||
app:tabIndicatorColor="@color/textsecure_primary"
|
||||
app:tabSelectedTextColor="@color/textsecure_primary"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white">
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/media_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<TextView android:id="@+id/no_images"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"
|
||||
android:paddingTop="30dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/media_overview_activity__no_media" />
|
||||
|
||||
</RelativeLayout>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
|
32
res/layout/media_overview_document_item.xml
Normal file
32
res/layout/media_overview_document_item.xml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.DocumentView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/document_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
app:documentForegroundTintColor="?attr/media_overview_document_foreground"
|
||||
app:documentBackgroundTintColor="?attr/media_overview_document_background"
|
||||
android:visibility="visible"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<TextView android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?attr/media_overview_document_foreground"
|
||||
android:paddingTop="20dp"
|
||||
tools:text="Jun 1"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -3,14 +3,14 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:background="?attr/media_overview_toolbar_background"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textColor="@color/gray50"
|
||||
android:textColor="?attr/media_overview_header_foreground"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textAllCaps="true"
|
22
res/layout/media_overview_documents_fragment.xml
Normal file
22
res/layout/media_overview_documents_fragment.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/media_overview_toolbar_background">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<TextView android:id="@+id/no_documents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:text="@string/media_overview_documents_fragment__no_documents_found" />
|
||||
|
||||
</RelativeLayout>
|
23
res/layout/media_overview_gallery_fragment.xml
Normal file
23
res/layout/media_overview_gallery_fragment.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/media_overview_toolbar_background">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/media_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<TextView android:id="@+id/no_images"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"
|
||||
android:paddingTop="30dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/media_overview_activity__no_media" />
|
||||
|
||||
</RelativeLayout>
|
18
res/layout/media_overview_gallery_item_header.xml
Normal file
18
res/layout/media_overview_gallery_item_header.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/media_overview_toolbar_background"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textColor="?attr/media_overview_header_foreground"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textAllCaps="true"
|
||||
tools:text="March 1, 2015" />
|
||||
</FrameLayout>
|
|
@ -3,6 +3,5 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/save"
|
||||
android:title="@string/media_overview__save_all"
|
||||
android:icon="@drawable/ic_save_all_white_24dp"
|
||||
app:showAsAction="ifRoom"/>
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
|
|
|
@ -130,6 +130,12 @@
|
|||
|
||||
<attr name="verification_background" format="color"/>
|
||||
|
||||
<attr name="media_overview_toolbar_background" format="color"/>
|
||||
<attr name="media_overview_toolbar_foreground" format="color"/>
|
||||
<attr name="media_overview_header_foreground" format="color"/>
|
||||
<attr name="media_overview_document_background" format="color"/>
|
||||
<attr name="media_overview_document_foreground" format="color"/>
|
||||
|
||||
<declare-styleable name="ColorPreference">
|
||||
<attr name="itemLayout" format="reference" />
|
||||
<attr name="choices" format="reference" />
|
||||
|
|
|
@ -1473,6 +1473,9 @@
|
|||
<string name="BucketedThreadMedia_Yesterday">Yesterday</string>
|
||||
<string name="BucketedThreadMedia_This_week">This week</string>
|
||||
<string name="BucketedThreadMedia_This_month">This month</string>
|
||||
<string name="MediaOverviewActivity_Media">Media</string>
|
||||
<string name="MediaOverviewActivity_Documents">Documents</string>
|
||||
<string name="media_overview_documents_fragment__no_documents_found">No documents found</string>
|
||||
|
||||
|
||||
<!-- EOF -->
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
<item name="dialog_background_color">@color/background_material_light</item>
|
||||
<item name="pref_divider">@drawable/preference_divider_light</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
|
||||
<item name="media_overview_toolbar_background">@color/white</item>
|
||||
<item name="media_overview_toolbar_foreground">@color/gray70</item>
|
||||
<item name="media_overview_header_foreground">@color/gray50</item>
|
||||
<item name="media_overview_document_foreground">@color/gray70</item>
|
||||
<item name="media_overview_document_background">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkNoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
|
||||
|
@ -38,6 +44,12 @@
|
|||
<item name="dialog_background_color">@color/background_material_dark</item>
|
||||
<item name="pref_divider">@drawable/preference_divider_dark</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
|
||||
<item name="media_overview_toolbar_background">@color/black</item>
|
||||
<item name="media_overview_toolbar_foreground">@color/white</item>
|
||||
<item name="media_overview_header_foreground">@color/gray10</item>
|
||||
<item name="media_overview_document_foreground">@color/white</item>
|
||||
<item name="media_overview_document_background">@color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.HighlightTheme" parent="@style/TextSecure.LightTheme">
|
||||
|
|
132
src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java
Normal file
132
src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java
Normal file
|
@ -0,0 +1,132 @@
|
|||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.MediaDocumentsAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.MediaDocumentsAdapter.ViewHolder;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager.TAG;
|
||||
|
||||
public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder> implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder> {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final Calendar calendar;
|
||||
private final Locale locale;
|
||||
|
||||
public MediaDocumentsAdapter(Context context, MasterSecret masterSecret, Cursor cursor, Locale locale) {
|
||||
super(context, cursor);
|
||||
|
||||
this.masterSecret = masterSecret;
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.media_overview_document_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null && slide.hasDocument()) {
|
||||
viewHolder.documentView.setDocument((DocumentSlide)slide, false);
|
||||
viewHolder.date.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate()));
|
||||
viewHolder.documentView.setVisibility(View.VISIBLE);
|
||||
viewHolder.date.setVisibility(View.VISIBLE);
|
||||
viewHolder.documentView.setOnClickListener(view -> {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.setDataAndType(PartAuthority.getAttachmentPublicUri(slide.getUri()), slide.getContentType());
|
||||
try {
|
||||
getContext().startActivity(intent);
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
Log.w(TAG, "No activity existed to view the media.");
|
||||
Toast.makeText(getContext(), R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
viewHolder.documentView.setVisibility(View.GONE);
|
||||
viewHolder.date.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
if (isHeaderPosition(position)) return -1;
|
||||
if (isFooterPosition(position)) return -1;
|
||||
if (position >= getItemCount()) return -1;
|
||||
if (position < 0) return -1;
|
||||
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
|
||||
calendar.setTime(new Date(mediaRecord.getDate()));
|
||||
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.media_overview_document_item_header, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
viewHolder.textView.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate()));
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final DocumentView documentView;
|
||||
private final TextView date;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.documentView = (DocumentView)itemView.findViewById(R.id.document_view);
|
||||
this.date = (TextView)itemView.findViewById(R.id.date);
|
||||
}
|
||||
}
|
||||
|
||||
static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final TextView textView;
|
||||
|
||||
HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.textView = (TextView)itemView.findViewById(R.id.text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -35,8 +35,8 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
|||
|
||||
import java.util.Locale;
|
||||
|
||||
public class MediaAdapter extends StickyHeaderGridAdapter {
|
||||
private static final String TAG = MediaAdapter.class.getSimpleName();
|
||||
public class MediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
private static final String TAG = MediaGalleryAdapter.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
|
@ -63,7 +63,7 @@ public class MediaAdapter extends StickyHeaderGridAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
public MediaAdapter(Context context, MasterSecret masterSecret, BucketedThreadMedia media, Locale locale, Address address) {
|
||||
public MediaGalleryAdapter(Context context, MasterSecret masterSecret, BucketedThreadMedia media, Locale locale, Address address) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.locale = locale;
|
||||
|
@ -77,12 +77,12 @@ public class MediaAdapter extends StickyHeaderGridAdapter {
|
|||
|
||||
@Override
|
||||
public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
|
||||
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_item_header, parent, false));
|
||||
return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_gallery_item_header, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_item, parent, false));
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_gallery_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -16,55 +16,59 @@
|
|||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
|
||||
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Activity for displaying media attachments in-app
|
||||
*/
|
||||
public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks<BucketedThreadMedia> {
|
||||
public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
private final static String TAG = MediaOverviewActivity.class.getSimpleName();
|
||||
|
||||
public static final String ADDRESS_EXTRA = "address";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private Toolbar toolbar;
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager viewPager;
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
private RecyclerView gridView;
|
||||
private StickyHeaderGridLayoutManager gridManager;
|
||||
private TextView noImages;
|
||||
private Recipient recipient;
|
||||
|
||||
@Override
|
||||
|
@ -75,24 +79,14 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
|
|||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||
setContentView(R.layout.media_overview_activity);
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.media_overview_activity);
|
||||
|
||||
initializeResources();
|
||||
initializeActionBar();
|
||||
initializeToolbar();
|
||||
|
||||
getSupportLoaderManager().initLoader(0, null, MediaOverviewActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (gridManager != null) {
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
|
||||
this.gridView.setLayoutManager(gridManager);
|
||||
}
|
||||
this.tabLayout.setupWithViewPager(viewPager);
|
||||
this.viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,106 +96,185 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
|
|||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
private void initializeActionBar() {
|
||||
getSupportActionBar().setTitle(recipient.toShortString());
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.noImages = ViewUtil.findById(this, R.id.no_images);
|
||||
this.gridView = ViewUtil.findById(this, R.id.media_grid);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
|
||||
|
||||
Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA);
|
||||
|
||||
this.recipient = Recipient.from(this, address, true);
|
||||
this.recipient.addListener(recipient -> initializeActionBar());
|
||||
|
||||
this.gridView.setAdapter(new MediaAdapter(this, masterSecret, new BucketedThreadMedia(this), dynamicLanguage.getCurrentLocale(), address));
|
||||
this.gridView.setLayoutManager(gridManager);
|
||||
this.gridView.setHasFixedSize(true);
|
||||
}
|
||||
|
||||
private void saveToDisk() {
|
||||
final Context c = this;
|
||||
|
||||
SaveAttachmentTask.showWarningDialog(this, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
new ProgressDialogAsyncTask<Void, Void, List<SaveAttachmentTask.Attachment>>(c,
|
||||
R.string.ConversationFragment_collecting_attahments,
|
||||
R.string.please_wait) {
|
||||
@Override
|
||||
protected List<SaveAttachmentTask.Attachment> doInBackground(Void... params) {
|
||||
long threadId = DatabaseFactory.getThreadDatabase(c).getThreadIdFor(recipient);
|
||||
Cursor cursor = DatabaseFactory.getMediaDatabase(c).getMediaForThread(threadId);
|
||||
List<SaveAttachmentTask.Attachment> attachments = new ArrayList<>(cursor.getCount());
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
MediaRecord record = MediaRecord.from(c, masterSecret, cursor);
|
||||
attachments.add(new SaveAttachmentTask.Attachment(record.getAttachment().getDataUri(),
|
||||
record.getContentType(),
|
||||
record.getDate(),
|
||||
null));
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<SaveAttachmentTask.Attachment> attachments) {
|
||||
super.onPostExecute(attachments);
|
||||
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(c, masterSecret, gridView, attachments.size());
|
||||
saveTask.execute(attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()]));
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}, gridView.getAdapter().getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
menu.clear();
|
||||
if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) {
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
inflater.inflate(R.menu.media_overview, menu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.save: saveToDisk(); return true;
|
||||
case android.R.id.home: finish(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA);
|
||||
|
||||
this.viewPager = ViewUtil.findById(this, R.id.pager);
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
this.tabLayout = ViewUtil.findById(this, R.id.tab_layout);
|
||||
this.recipient = Recipient.from(this, address, true);
|
||||
}
|
||||
|
||||
private void initializeToolbar() {
|
||||
setSupportActionBar(this.toolbar);
|
||||
getSupportActionBar().setTitle(recipient.toShortString());
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
this.recipient.addListener(recipient -> getSupportActionBar().setTitle(recipient.toShortString()));
|
||||
}
|
||||
|
||||
private class MediaOverviewPagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
MediaOverviewPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
Fragment fragment;
|
||||
|
||||
if (position == 0) fragment = new MediaOverviewGalleryFragment();
|
||||
else if (position == 1) fragment = new MediaOverviewDocumentsFragment();
|
||||
else throw new AssertionError();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(MediaOverviewGalleryFragment.ADDRESS_EXTRA, recipient.getAddress().serialize());
|
||||
args.putParcelable(MediaOverviewGalleryFragment.MASTER_SECRET_EXTRA, masterSecret);
|
||||
args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, dynamicLanguage.getCurrentLocale());
|
||||
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
if (position == 0) return getString(R.string.MediaOverviewActivity_Media);
|
||||
else if (position == 1) return getString(R.string.MediaOverviewActivity_Documents);
|
||||
else throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class MediaOverviewFragment<T> extends Fragment implements LoaderManager.LoaderCallbacks<T> {
|
||||
|
||||
public static final String ADDRESS_EXTRA = "address";
|
||||
public static final String MASTER_SECRET_EXTRA = "master_secret";
|
||||
public static final String LOCALE_EXTRA = "locale_extra";
|
||||
|
||||
protected TextView noMedia;
|
||||
protected Recipient recipient;
|
||||
protected MasterSecret masterSecret;
|
||||
protected RecyclerView recyclerView;
|
||||
protected Locale locale;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
String address = getArguments().getString(ADDRESS_EXTRA);
|
||||
MasterSecret masterSecret = getArguments().getParcelable(MASTER_SECRET_EXTRA);
|
||||
Locale locale = (Locale)getArguments().getSerializable(LOCALE_EXTRA);
|
||||
|
||||
if (address == null) throw new AssertionError();
|
||||
if (masterSecret == null) throw new AssertionError();
|
||||
if (locale == null) throw new AssertionError();
|
||||
|
||||
this.recipient = Recipient.from(getContext(), Address.fromSerialized(address), true);
|
||||
this.masterSecret = masterSecret;
|
||||
this.locale = locale;
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MediaOverviewGalleryFragment extends MediaOverviewFragment<BucketedThreadMedia> {
|
||||
|
||||
private StickyHeaderGridLayoutManager gridManager;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.media_overview_gallery_fragment, container, false);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(view, R.id.media_grid);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_images);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
|
||||
|
||||
this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), masterSecret, new BucketedThreadMedia(getContext()), locale, recipient.getAddress()));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
this.recyclerView.setHasFixedSize(true);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (gridManager != null) {
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(this, masterSecret, recipient.getAddress());
|
||||
return new BucketedThreadMediaLoader(getContext(), masterSecret, recipient.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<BucketedThreadMedia> loader, BucketedThreadMedia bucketedThreadMedia) {
|
||||
((MediaAdapter)gridView.getAdapter()).setMedia(bucketedThreadMedia);
|
||||
((MediaAdapter)gridView.getAdapter()).notifyAllSectionsDataSetChanged();
|
||||
((MediaGalleryAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
|
||||
((MediaGalleryAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
|
||||
|
||||
noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
|
||||
invalidateOptionsMenu();
|
||||
noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<BucketedThreadMedia> cursorLoader) {
|
||||
((MediaAdapter)gridView.getAdapter()).setMedia(new BucketedThreadMedia(this));
|
||||
((MediaGalleryAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMedia(getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MediaOverviewDocumentsFragment extends MediaOverviewFragment<Cursor> {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.media_overview_documents_fragment, container, false);
|
||||
MediaDocumentsAdapter adapter = new MediaDocumentsAdapter(getContext(), masterSecret, null, locale);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
|
||||
|
||||
this.recyclerView.setAdapter(adapter);
|
||||
this.recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
|
||||
this.recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter, false, true));
|
||||
this.recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new ThreadMediaLoader(getContext(), masterSecret, recipient.getAddress(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
((CursorRecyclerViewAdapter)this.recyclerView.getAdapter()).changeCursor(data);
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
this.noMedia.setVisibility(data.getCount() > 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
((CursorRecyclerViewAdapter)this.recyclerView.getAdapter()).changeCursor(null);
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
|||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new ThreadMediaLoader(this, masterSecret, address);
|
||||
return new ThreadMediaLoader(this, masterSecret, address, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|||
|
||||
public class MediaDatabase extends Database {
|
||||
|
||||
private final static String MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS + ", "
|
||||
private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL_ASPECT_RATIO + ", "
|
||||
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", "
|
||||
|
@ -37,19 +37,27 @@ public class MediaDatabase extends Database {
|
|||
+ " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " "
|
||||
+ "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
|
||||
+ " FROM " + MmsDatabase.TABLE_NAME
|
||||
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND ("
|
||||
+ AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' OR "
|
||||
+ AttachmentDatabase.CONTENT_TYPE + " LIKE 'video/%') AND "
|
||||
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND (%s) AND "
|
||||
+ AttachmentDatabase.DATA + " IS NOT NULL "
|
||||
+ "ORDER BY " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " DESC";
|
||||
|
||||
private static final String GALLERY_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' OR " + AttachmentDatabase.CONTENT_TYPE + " LIKE 'video/%'");
|
||||
private static final String DOCUMENT_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'image/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'video/%'");
|
||||
|
||||
public MediaDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getMediaForThread(long threadId) {
|
||||
public Cursor getGalleryMediaForThread(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = database.rawQuery(MEDIA_QUERY, new String[]{threadId+""});
|
||||
Cursor cursor = database.rawQuery(GALLERY_MEDIA_QUERY, new String[]{threadId+""});
|
||||
setNotifyConverationListeners(cursor, threadId);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public Cursor getDocumentMediaForThread(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = database.rawQuery(DOCUMENT_MEDIA_QUERY, new String[]{threadId+""});
|
||||
setNotifyConverationListeners(cursor, threadId);
|
||||
return cursor;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMed
|
|||
BucketedThreadMedia result = new BucketedThreadMedia(getContext());
|
||||
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true));
|
||||
|
||||
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getMediaForThread(threadId)) {
|
||||
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
result.add(MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor));
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.Database;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
|
||||
|
@ -15,17 +16,21 @@ public class ThreadMediaLoader extends AbstractCursorLoader {
|
|||
|
||||
private final Address address;
|
||||
private final MasterSecret masterSecret;
|
||||
private final boolean gallery;
|
||||
|
||||
public ThreadMediaLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Address address) {
|
||||
public ThreadMediaLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Address address, boolean gallery) {
|
||||
super(context);
|
||||
this.masterSecret = masterSecret;
|
||||
this.address = address;
|
||||
this.gallery = gallery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getCursor() {
|
||||
long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true));
|
||||
return DatabaseFactory.getMediaDatabase(getContext()).getMediaForThread(threadId);
|
||||
|
||||
if (gallery) return DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId);
|
||||
else return DatabaseFactory.getMediaDatabase(getContext()).getDocumentMediaForThread(threadId);
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
|
|
Loading…
Add table
Reference in a new issue