Refactor media overview activity to display documents

// FREEBIE
pull/1/head
Moxie Marlinspike 7 years ago
parent c6b2e785a5
commit 8ce914a344

@ -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="match_parent"
android:background="@color/white">
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.v7.widget.RecyclerView
android:id="@+id/media_grid"
<android.support.v4.view.ViewPager
android:id="@+id/pager"
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" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>

@ -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"

@ -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>

@ -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>

@ -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">

@ -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,56 +16,60 @@
*/
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;
private Recipient recipient;
@Override
protected void onPreCreate() {
@ -75,24 +79,14 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
@Override
protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
this.masterSecret = masterSecret;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.media_overview_activity);
this.masterSecret = masterSecret;
initializeResources();
initializeActionBar();
getSupportLoaderManager().initLoader(0, null, MediaOverviewActivity.this);
}
initializeToolbar();
@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());
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
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.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);
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());
private void initializeToolbar() {
setSupportActionBar(this.toolbar);
getSupportActionBar().setTitle(recipient.toShortString());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
this.recipient.addListener(recipient -> getSupportActionBar().setTitle(recipient.toShortString()));
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
private class MediaOverviewPagerAdapter extends FragmentStatePagerAdapter {
menu.clear();
if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) {
MenuInflater inflater = this.getMenuInflater();
inflater.inflate(R.menu.media_overview, menu);
MediaOverviewPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
return true;
}
@Override
public Fragment getItem(int position) {
Fragment fragment;
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
if (position == 0) fragment = new MediaOverviewGalleryFragment();
else if (position == 1) fragment = new MediaOverviewDocumentsFragment();
else throw new AssertionError();
switch (item.getItemId()) {
case R.id.save: saveToDisk(); return true;
case android.R.id.home: finish(); return true;
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;
}
return false;
@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();
}
}
@Override
public Loader<BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
return new BucketedThreadMediaLoader(this, masterSecret, recipient.getAddress());
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);
}
}
@Override
public void onLoadFinished(Loader<BucketedThreadMedia> loader, BucketedThreadMedia bucketedThreadMedia) {
((MediaAdapter)gridView.getAdapter()).setMedia(bucketedThreadMedia);
((MediaAdapter)gridView.getAdapter()).notifyAllSectionsDataSetChanged();
public static class MediaOverviewGalleryFragment extends MediaOverviewFragment<BucketedThreadMedia> {
private StickyHeaderGridLayoutManager gridManager;
noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
invalidateOptionsMenu();
@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(getContext(), masterSecret, recipient.getAddress());
}
@Override
public void onLoadFinished(Loader<BucketedThreadMedia> loader, BucketedThreadMedia bucketedThreadMedia) {
((MediaGalleryAdapter) recyclerView.getAdapter()).setMedia(bucketedThreadMedia);
((MediaGalleryAdapter) recyclerView.getAdapter()).notifyAllSectionsDataSetChanged();
noMedia.setVisibility(recyclerView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
getActivity().invalidateOptionsMenu();
}
@Override
public void onLoaderReset(Loader<BucketedThreadMedia> cursorLoader) {
((MediaGalleryAdapter) recyclerView.getAdapter()).setMedia(new BucketedThreadMedia(getContext()));
}
}
@Override
public void onLoaderReset(Loader<BucketedThreadMedia> cursorLoader) {
((MediaAdapter)gridView.getAdapter()).setMedia(new BucketedThreadMedia(this));
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(GALLERY_MEDIA_QUERY, new String[]{threadId+""});
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public Cursor getDocumentMediaForThread(long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.rawQuery(MEDIA_QUERY, new String[]{threadId+""});
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…
Cancel
Save