diff --git a/build.gradle b/build.gradle
index 9c84e83d7e..9c9138e2ea 100644
--- a/build.gradle
+++ b/build.gradle
@@ -105,6 +105,7 @@ dependencies {
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
+ compile 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
testCompile 'junit:junit:4.12'
@@ -171,6 +172,7 @@ dependencyVerification {
'com.klinkerapps:android-smsmms:e7c3328a0f3a8dd44daa8129de4e99996f3057a4546e47891b036b81e0ebf1d1',
'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9',
'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1',
+ 'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794',
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
diff --git a/res/layout/media_overview_activity.xml b/res/layout/media_overview_activity.xml
index d9e3d7afb4..e1aa997ce8 100644
--- a/res/layout/media_overview_activity.xml
+++ b/res/layout/media_overview_activity.xml
@@ -4,7 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/gray95">
+ android:background="@color/white">
diff --git a/res/layout/media_overview_item.xml b/res/layout/media_overview_item.xml
index 715651a9c5..5c649bdcbb 100644
--- a/res/layout/media_overview_item.xml
+++ b/res/layout/media_overview_item.xml
@@ -2,7 +2,8 @@
+ android:layout_height="match_parent"
+ android:padding="2dp">
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6a9e364e55..c12f1bb47f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1469,6 +1469,10 @@
Default alarm sound
Add ringtone
Unable to add custom ringtone
+ Today
+ Yesterday
+ This week
+ This month
diff --git a/src/org/thoughtcrime/securesms/MediaAdapter.java b/src/org/thoughtcrime/securesms/MediaAdapter.java
index 4d2392ff50..04554c99c7 100644
--- a/src/org/thoughtcrime/securesms/MediaAdapter.java
+++ b/src/org/thoughtcrime/securesms/MediaAdapter.java
@@ -18,65 +18,104 @@ package org.thoughtcrime.securesms;
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.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
-import org.thoughtcrime.securesms.MediaAdapter.ViewHolder;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
-import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
+import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.util.MediaUtil;
-public class MediaAdapter extends CursorRecyclerViewAdapter {
+import java.util.Locale;
+
+public class MediaAdapter extends StickyHeaderGridAdapter {
private static final String TAG = MediaAdapter.class.getSimpleName();
- private final MasterSecret masterSecret;
- private final Address address;
+ private final Context context;
+ private final MasterSecret masterSecret;
+ private final Locale locale;
+ private final Address address;
+
+ private BucketedThreadMedia media;
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public ThumbnailView imageView;
+ private static class ViewHolder extends StickyHeaderGridAdapter.ItemViewHolder {
+ ThumbnailView imageView;
- public ViewHolder(View v) {
+ ViewHolder(View v) {
super(v);
imageView = (ThumbnailView) v.findViewById(R.id.image);
}
}
- public MediaAdapter(Context context, MasterSecret masterSecret, Cursor c, Address address) {
- super(context, c);
+ private static class HeaderHolder extends StickyHeaderGridAdapter.HeaderViewHolder {
+ TextView textView;
+
+ HeaderHolder(View itemView) {
+ super(itemView);
+ textView = (TextView) itemView.findViewById(R.id.text);
+ }
+ }
+
+ public MediaAdapter(Context context, MasterSecret masterSecret, BucketedThreadMedia media, Locale locale, Address address) {
+ this.context = context;
this.masterSecret = masterSecret;
+ this.locale = locale;
+ this.media = media;
this.address = address;
}
+ public void setMedia(BucketedThreadMedia media) {
+ this.media = media;
+ }
+
@Override
- public ViewHolder onCreateItemViewHolder(final ViewGroup viewGroup, final int i) {
- final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_overview_item, viewGroup, false);
- return new ViewHolder(view);
+ public StickyHeaderGridAdapter.HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType) {
+ return new HeaderHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_item_header, parent, false));
}
@Override
- public void onBindItemViewHolder(final ViewHolder viewHolder, final @NonNull Cursor cursor) {
- final ThumbnailView imageView = viewHolder.imageView;
- final MediaRecord mediaRecord = MediaRecord.from(getContext(), masterSecret, cursor);
+ public ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType) {
+ return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.media_overview_item, parent, false));
+ }
- Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
+ @Override
+ public void onBindHeaderViewHolder(StickyHeaderGridAdapter.HeaderViewHolder viewHolder, int section) {
+ ((HeaderHolder)viewHolder).textView.setText(media.getName(section, locale));
+ }
+
+ @Override
+ public void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int offset) {
+ MediaRecord mediaRecord = media.get(section, offset);
+ ThumbnailView thumbnailView = ((ViewHolder)viewHolder).imageView;
+
+ Slide slide = MediaUtil.getSlideForAttachment(context, mediaRecord.getAttachment());
if (slide != null) {
- imageView.setImageResource(masterSecret, slide, false, false);
+ thumbnailView.setImageResource(masterSecret, slide, false, false);
}
- imageView.setOnClickListener(new OnMediaClickListener(mediaRecord));
+ thumbnailView.setOnClickListener(new OnMediaClickListener(mediaRecord));
+ }
+
+ @Override
+ public int getSectionCount() {
+ return media.getSectionCount();
}
- private class OnMediaClickListener implements OnClickListener {
+ @Override
+ public int getSectionItemCount(int section) {
+ return media.getSectionItemCount(section);
+ }
+
+ private class OnMediaClickListener implements View.OnClickListener {
+
private final MediaRecord mediaRecord;
private OnMediaClickListener(MediaRecord mediaRecord) {
@@ -86,7 +125,7 @@ public class MediaAdapter extends CursorRecyclerViewAdapter {
@Override
public void onClick(View v) {
if (mediaRecord.getAttachment().getDataUri() != null) {
- Intent intent = new Intent(getContext(), MediaPreviewActivity.class);
+ Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize());
intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, address);
@@ -96,8 +135,9 @@ public class MediaAdapter extends CursorRecyclerViewAdapter {
}
intent.setDataAndType(mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType());
- getContext().startActivity(intent);
+ context.startActivity(intent);
}
}
}
+
}
diff --git a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java
index 73ad46f2dc..17363edf5a 100644
--- a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java
+++ b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java
@@ -16,37 +16,34 @@
*/
package org.thoughtcrime.securesms;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.database.Cursor;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
-import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.WindowManager;
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.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
-import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
+import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
+import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DynamicLanguage;
+import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
+import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.util.ArrayList;
@@ -55,95 +52,73 @@ import java.util.List;
/**
* Activity for displaying media attachments in-app
*/
-public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks {
+public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks {
private final static String TAG = MediaOverviewActivity.class.getSimpleName();
public static final String ADDRESS_EXTRA = "address";
+ private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private MasterSecret masterSecret;
private RecyclerView gridView;
- private GridLayoutManager gridManager;
+ private StickyHeaderGridLayoutManager gridManager;
private TextView noImages;
private Recipient recipient;
@Override
protected void onPreCreate() {
- this.setTheme(R.style.TextSecure_DarkTheme);
+ dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
this.masterSecret = masterSecret;
- setFullscreenIfPossible();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.media_overview_activity);
initializeResources();
initializeActionBar();
+
getSupportLoaderManager().initLoader(0, null, MediaOverviewActivity.this);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (gridManager != null) gridManager.setSpanCount(getResources().getInteger(R.integer.media_overview_cols));
- }
-
- @TargetApi(VERSION_CODES.JELLY_BEAN)
- private void setFullscreenIfPossible() {
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
- getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
+ if (gridManager != null) {
+ this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
+ this.gridView.setLayoutManager(gridManager);
}
}
@Override
public void onResume() {
super.onResume();
+ dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
private void initializeActionBar() {
- getSupportActionBar().setTitle(recipient == null
- ? getString(R.string.AndroidManifest__all_media)
- : getString(R.string.AndroidManifest__all_media_named, recipient.toShortString()));
- }
-
- @Override
- public void onPause() {
- super.onPause();
+ getSupportActionBar().setTitle(recipient.toShortString());
}
private void initializeResources() {
- noImages = (TextView ) findViewById(R.id.no_images );
- gridView = (RecyclerView) findViewById(R.id.media_grid);
- gridManager = new GridLayoutManager(this, getResources().getInteger(R.integer.media_overview_cols));
- gridView.setLayoutManager(gridManager);
- gridView.setHasFixedSize(true);
+ 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);
- if (address != null) {
- recipient = Recipient.from(this, address, true);
- } else {
- recipient = null;
- }
+ this.recipient = Recipient.from(this, address, true);
+ this.recipient.addListener(recipient -> initializeActionBar());
- if (recipient != null) {
- recipient.addListener(new RecipientModifiedListener() {
- @Override
- public void onModified(Recipient recipients) {
- 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() {
@@ -212,21 +187,21 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
}
@Override
- public Loader onCreateLoader(int i, Bundle bundle) {
- return new ThreadMediaLoader(this, masterSecret, recipient.getAddress());
+ public Loader onCreateLoader(int i, Bundle bundle) {
+ return new BucketedThreadMediaLoader(this, masterSecret, recipient.getAddress());
}
@Override
- public void onLoadFinished(Loader cursorLoader, Cursor cursor) {
- Log.w(TAG, "onLoadFinished()");
- gridView.setAdapter(new MediaAdapter(this, masterSecret, cursor, recipient.getAddress()));
+ public void onLoadFinished(Loader loader, BucketedThreadMedia bucketedThreadMedia) {
+ ((MediaAdapter)gridView.getAdapter()).setMedia(bucketedThreadMedia);
+ ((MediaAdapter)gridView.getAdapter()).notifyAllSectionsDataSetChanged();
+
noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
invalidateOptionsMenu();
}
@Override
- public void onLoaderReset(Loader cursorLoader) {
- ((CursorRecyclerViewAdapter)gridView.getAdapter()).changeCursor(null);
+ public void onLoaderReset(Loader cursorLoader) {
+ ((MediaAdapter)gridView.getAdapter()).setMedia(new BucketedThreadMedia(this));
}
-
}
diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java
new file mode 100644
index 0000000000..4d4f85942b
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java
@@ -0,0 +1,221 @@
+package org.thoughtcrime.securesms.database.loaders;
+
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.v4.content.AsyncTaskLoader;
+
+import com.annimon.stream.Stream;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MediaDatabase;
+import org.thoughtcrime.securesms.recipients.Recipient;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class BucketedThreadMediaLoader extends AsyncTaskLoader {
+
+ private static final String TAG = BucketedThreadMediaLoader.class.getSimpleName();
+
+ private final MasterSecret masterSecret;
+ private final Address address;
+
+ public BucketedThreadMediaLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Address address) {
+ super(context);
+ this.masterSecret = masterSecret;
+ this.address = address;
+
+ onContentChanged();
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (takeContentChanged()) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public BucketedThreadMedia loadInBackground() {
+ BucketedThreadMedia result = new BucketedThreadMedia(getContext());
+ long threadId = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(Recipient.from(getContext(), address, true));
+
+ try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getMediaForThread(threadId)) {
+ while (cursor != null && cursor.moveToNext()) {
+ result.add(MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor));
+ }
+ }
+
+ return result;
+ }
+
+ public static class BucketedThreadMedia {
+
+ private final TimeBucket TODAY;
+ private final TimeBucket YESTERDAY;
+ private final TimeBucket THIS_WEEK;
+ private final TimeBucket THIS_MONTH;
+ private final MonthBuckets OLDER;
+
+ private final TimeBucket[] TIME_SECTIONS;
+
+ public BucketedThreadMedia(@NonNull Context context) {
+ this.TODAY = new TimeBucket(context.getString(R.string.BucketedThreadMedia_Today), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -1), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, 1000));
+ this.YESTERDAY = new TimeBucket(context.getString(R.string.BucketedThreadMedia_Yesterday), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -2), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -1));
+ this.THIS_WEEK = new TimeBucket(context.getString(R.string.BucketedThreadMedia_This_week), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -7), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -2));
+ this.THIS_MONTH = new TimeBucket(context.getString(R.string.BucketedThreadMedia_This_month), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -30), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -7));
+ this.TIME_SECTIONS = new TimeBucket[]{TODAY, YESTERDAY, THIS_WEEK, THIS_MONTH};
+ this.OLDER = new MonthBuckets();
+ }
+
+
+ public void add(MediaDatabase.MediaRecord mediaRecord) {
+ for (TimeBucket timeSection : TIME_SECTIONS) {
+ if (timeSection.inRange(mediaRecord.getDate())) {
+ timeSection.add(mediaRecord);
+ return;
+ }
+ }
+
+ OLDER.add(mediaRecord);
+ }
+
+ public int getSectionCount() {
+ return (int)Stream.of(TIME_SECTIONS)
+ .filter(timeBucket -> !timeBucket.isEmpty())
+ .count() +
+ OLDER.getSectionCount();
+ }
+
+ public int getSectionItemCount(int section) {
+ List activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
+
+ if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getItemCount();
+ else return OLDER.getSectionItemCount(section - activeTimeBuckets.size());
+ }
+
+ public MediaDatabase.MediaRecord get(int section, int item) {
+ List activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
+
+ if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getItem(item);
+ else return OLDER.getItem(section - activeTimeBuckets.size(), item);
+ }
+
+ public String getName(int section, Locale locale) {
+ List activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
+
+ if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getName();
+ else return OLDER.getName(section - activeTimeBuckets.size(), locale);
+ }
+
+ private static class TimeBucket {
+
+ private final List records = new LinkedList<>();
+
+ private final long startTime;
+ private final long endtime;
+ private final String name;
+
+ TimeBucket(String name, long startTime, long endtime) {
+ this.name = name;
+ this.startTime = startTime;
+ this.endtime = endtime;
+ }
+
+ void add(MediaDatabase.MediaRecord record) {
+ this.records.add(record);
+ }
+
+ boolean inRange(long timestamp) {
+ return timestamp > startTime && timestamp <= endtime;
+ }
+
+ boolean isEmpty() {
+ return records.isEmpty();
+ }
+
+ int getItemCount() {
+ return records.size();
+ }
+
+ MediaDatabase.MediaRecord getItem(int position) {
+ return records.get(position);
+ }
+
+ String getName() {
+ return name;
+ }
+
+ static long addToCalendar(int field, int amount) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(field, amount);
+ return calendar.getTimeInMillis();
+ }
+ }
+
+ private static class MonthBuckets {
+
+ private final Map> months = new HashMap<>();
+
+ void add(MediaDatabase.MediaRecord record) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(record.getDate());
+
+ int year = calendar.get(Calendar.YEAR) - 1900;
+ int month = calendar.get(Calendar.MONTH);
+ Date date = new Date(year, month, 1);
+
+ if (months.containsKey(date)) {
+ months.get(date).add(record);
+ } else {
+ List list = new LinkedList<>();
+ list.add(record);
+ months.put(date, list);
+ }
+ }
+
+ int getSectionCount() {
+ return months.size();
+ }
+
+ int getSectionItemCount(int section) {
+ return months.get(getSection(section)).size();
+ }
+
+ MediaDatabase.MediaRecord getItem(int section, int position) {
+ return months.get(getSection(section)).get(position);
+ }
+
+ Date getSection(int section) {
+ ArrayList keys = new ArrayList<>(months.keySet());
+ Collections.sort(keys, Collections.reverseOrder());
+
+ return keys.get(section);
+ }
+
+ String getName(int section, Locale locale) {
+ Date sectionDate = getSection(section);
+
+ return new SimpleDateFormat("MMMM, yyyy", locale).format(sectionDate);
+ }
+ }
+ }
+}