mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-18 22:41:33 +00:00
"All images" view for conversations
// FREEBIE
This commit is contained in:
parent
d3271f548c
commit
5fac189736
@ -209,6 +209,10 @@
|
|||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".MediaOverviewActivity"
|
||||||
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".DummyActivity"
|
<activity android:name=".DummyActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -44,6 +44,7 @@ dependencies {
|
|||||||
compile 'com.afollestad:material-dialogs:0.6.1.5'
|
compile 'com.afollestad:material-dialogs:0.6.1.5'
|
||||||
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
|
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
|
||||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||||
|
compile 'com.android.support:recyclerview-v7:21.0.3'
|
||||||
compile 'com.melnykov:floatingactionbutton:1.1.0'
|
compile 'com.melnykov:floatingactionbutton:1.1.0'
|
||||||
compile 'com.google.zxing:android-integration:3.1.0'
|
compile 'com.google.zxing:android-integration:3.1.0'
|
||||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||||
@ -89,6 +90,7 @@ dependencyVerification {
|
|||||||
'com.afollestad:material-dialogs:ccb013e6572c86cfcca433855cf0dbfbff9b5e7bb9d1f504b761a6bc6f467b60',
|
'com.afollestad:material-dialogs:ccb013e6572c86cfcca433855cf0dbfbff9b5e7bb9d1f504b761a6bc6f467b60',
|
||||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||||
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
||||||
|
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
||||||
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
|
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
|
||||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||||
@ -96,7 +98,6 @@ dependencyVerification {
|
|||||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
||||||
'org.whispersystems:jobmanager:01f35586c43aa3806f1c18d3d6a5a972def98103ba1a5a9ca3eec08d15f974b7',
|
'org.whispersystems:jobmanager:01f35586c43aa3806f1c18d3d6a5a972def98103ba1a5a9ca3eec08d15f974b7',
|
||||||
'org.whispersystems:libpastelog:3ccf00fe1597eb8ca1e5de99b17fc225387a1b80b5bbc00ec1bc4d4f3ea9cdde',
|
'org.whispersystems:libpastelog:3ccf00fe1597eb8ca1e5de99b17fc225387a1b80b5bbc00ec1bc4d4f3ea9cdde',
|
||||||
'com.android.support:recyclerview-v7:ab2390d688601b65e2f3a0718b3d25487e61546c4e20f81eb0b033f30ca15b31',
|
|
||||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 768 B |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
25
res/layout/media_overview_activity.xml
Normal file
25
res/layout/media_overview_activity.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?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="@color/gray95">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/media_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/no_images"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:text="@string/media_overview_activity__no_images" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
15
res/layout/media_overview_item.xml
Normal file
15
res/layout/media_overview_item.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.components.SquareLinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:contentDescription="@string/media_preview_activity__image_content_description" />
|
||||||
|
|
||||||
|
</org.thoughtcrime.securesms.components.SquareLinearLayout>
|
@ -2,11 +2,12 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:title="@string/conversation__menu_add_attachment"
|
<item android:title="@string/conversation__menu_add_attachment"
|
||||||
android:id="@+id/menu_add_attachment"
|
android:id="@+id/menu_add_attachment" />
|
||||||
android:icon="@drawable/ic_menu_attach" />
|
|
||||||
|
<item android:title="@string/conversation__menu_view_media"
|
||||||
|
android:id="@+id/menu_view_media" />
|
||||||
|
|
||||||
<item android:title="@string/conversation__menu_delete_thread"
|
<item android:title="@string/conversation__menu_delete_thread"
|
||||||
android:id="@+id/menu_delete_thread"
|
android:id="@+id/menu_delete_thread" />
|
||||||
android:icon="@android:drawable/ic_menu_delete" />
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
4
res/values-land/dimens.xml
Normal file
4
res/values-land/dimens.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<integer name="media_overview_cols">5</integer>
|
||||||
|
</resources>
|
@ -14,4 +14,6 @@
|
|||||||
<dimen name="contact_selection_photo_size">50dp</dimen>
|
<dimen name="contact_selection_photo_size">50dp</dimen>
|
||||||
<dimen name="thumbnail_max_size">230dp</dimen>
|
<dimen name="thumbnail_max_size">230dp</dimen>
|
||||||
<dimen name="preference_fragment_padding_side">8dp</dimen>
|
<dimen name="preference_fragment_padding_side">8dp</dimen>
|
||||||
|
|
||||||
|
<integer name="media_overview_cols">3</integer>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -526,6 +526,9 @@
|
|||||||
<string name="import_fragment__import_a_plaintext_backup_file">
|
<string name="import_fragment__import_a_plaintext_backup_file">
|
||||||
Import a plaintext backup file. Compatible with \'SMSBackup And Restore.\'</string>
|
Import a plaintext backup file. Compatible with \'SMSBackup And Restore.\'</string>
|
||||||
|
|
||||||
|
<!-- media_overview_activity -->
|
||||||
|
<string name="media_overview_activity__no_images">No images</string>
|
||||||
|
|
||||||
<!-- MmsPreferencesFragment -->
|
<!-- MmsPreferencesFragment -->
|
||||||
<string name="MmsPreferencesFragment__manual_mms_settings_are_required">Manual MMS settings are required for your phone.</string>
|
<string name="MmsPreferencesFragment__manual_mms_settings_are_required">Manual MMS settings are required for your phone.</string>
|
||||||
<string name="MmsPreferencesFragment__enabled">Enabled</string>
|
<string name="MmsPreferencesFragment__enabled">Enabled</string>
|
||||||
@ -650,6 +653,8 @@
|
|||||||
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
|
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
|
||||||
<string name="AndroidManifest__log_submit">Submit debug logs</string>
|
<string name="AndroidManifest__log_submit">Submit debug logs</string>
|
||||||
<string name="AndroidManifest__media_preview">Media Preview</string>
|
<string name="AndroidManifest__media_preview">Media Preview</string>
|
||||||
|
<string name="AndroidManifest__media_overview">All images</string>
|
||||||
|
<string name="AndroidManifest__media_overview_named">All images with %1$s</string>
|
||||||
|
|
||||||
<!-- arrays.xml -->
|
<!-- arrays.xml -->
|
||||||
<string name="arrays__import_export">Import / export</string>
|
<string name="arrays__import_export">Import / export</string>
|
||||||
@ -823,6 +828,7 @@
|
|||||||
<string name="conversation__menu_update_group">Update group</string>
|
<string name="conversation__menu_update_group">Update group</string>
|
||||||
<string name="conversation__menu_leave_group">Leave group</string>
|
<string name="conversation__menu_leave_group">Leave group</string>
|
||||||
<string name="conversation__menu_delete_thread">Delete thread</string>
|
<string name="conversation__menu_delete_thread">Delete thread</string>
|
||||||
|
<string name="conversation__menu_view_media">All images</string>
|
||||||
|
|
||||||
<!-- conversation_callable -->
|
<!-- conversation_callable -->
|
||||||
<string name="conversation_add_to_contacts__menu_add_to_contacts">Add to contacts</string>
|
<string name="conversation_add_to_contacts__menu_add_to_contacts">Add to contacts</string>
|
||||||
|
@ -310,6 +310,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
case R.id.menu_call: handleDial(getRecipients().getPrimaryRecipient()); return true;
|
case R.id.menu_call: handleDial(getRecipients().getPrimaryRecipient()); return true;
|
||||||
case R.id.menu_delete_thread: handleDeleteThread(); return true;
|
case R.id.menu_delete_thread: handleDeleteThread(); return true;
|
||||||
case R.id.menu_add_attachment: handleAddAttachment(); return true;
|
case R.id.menu_add_attachment: handleAddAttachment(); return true;
|
||||||
|
case R.id.menu_view_media: handleViewMedia(); return true;
|
||||||
case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
|
case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
|
||||||
case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
|
case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
|
||||||
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
|
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
|
||||||
@ -423,6 +424,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleViewMedia() {
|
||||||
|
Intent intent = new Intent(this, MediaOverviewActivity.class);
|
||||||
|
intent.putExtra(MediaOverviewActivity.THREAD_ID_EXTRA, threadId);
|
||||||
|
intent.putExtra(MediaOverviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId());
|
||||||
|
intent.putExtra(MediaOverviewActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleLeavePushGroup() {
|
private void handleLeavePushGroup() {
|
||||||
if (getRecipients() == null) {
|
if (getRecipients() == null) {
|
||||||
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
|
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
|
||||||
|
118
src/org/thoughtcrime/securesms/ImageMediaAdapter.java
Normal file
118
src/org/thoughtcrime/securesms/ImageMediaAdapter.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ImageMediaAdapter.ViewHolder;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
|
import org.thoughtcrime.securesms.database.PartDatabase.ImageRecord;
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
|
||||||
|
import ws.com.google.android.mms.pdu.PduPart;
|
||||||
|
|
||||||
|
public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
|
||||||
|
private static final String TAG = ImageMediaAdapter.class.getSimpleName();
|
||||||
|
|
||||||
|
private final MasterSecret masterSecret;
|
||||||
|
private final int gridSize;
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public ImageView imageView;
|
||||||
|
|
||||||
|
public ViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
imageView = (ImageView) v.findViewById(R.id.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageMediaAdapter(Context context, MasterSecret masterSecret, Cursor c) {
|
||||||
|
super(context, c);
|
||||||
|
this.masterSecret = masterSecret;
|
||||||
|
this.gridSize = context.getResources().getDimensionPixelSize(R.dimen.thumbnail_max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(final ViewGroup viewGroup, final int i) {
|
||||||
|
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_overview_item, viewGroup, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final ViewHolder viewHolder, final Cursor cursor) {
|
||||||
|
final ImageView imageView = viewHolder.imageView;
|
||||||
|
final ImageRecord imageRecord = ImageRecord.from(cursor);
|
||||||
|
|
||||||
|
PduPart part = new PduPart();
|
||||||
|
|
||||||
|
part.setDataUri(imageRecord.getUri());
|
||||||
|
part.setContentType(imageRecord.getContentType().getBytes());
|
||||||
|
part.setId(imageRecord.getPartId());
|
||||||
|
|
||||||
|
Slide slide = MediaUtil.getSlideForPart(getContext(), masterSecret, part, imageRecord.getContentType());
|
||||||
|
if (slide != null) slide.setThumbnailOn(imageView, gridSize, gridSize, new ColorDrawable(0x11ffffff));
|
||||||
|
|
||||||
|
imageView.setOnClickListener(new OnMediaClickListener(imageRecord));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnMediaClickListener implements OnClickListener {
|
||||||
|
private ImageRecord record;
|
||||||
|
|
||||||
|
private OnMediaClickListener(ImageRecord record) {
|
||||||
|
this.record = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent intent = new Intent(getContext(), MediaPreviewActivity.class);
|
||||||
|
intent.putExtra(MediaPreviewActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||||
|
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, record.getDate());
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(record.getAddress())) {
|
||||||
|
try {
|
||||||
|
Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(),
|
||||||
|
record.getAddress(),
|
||||||
|
true);
|
||||||
|
if (recipients != null && recipients.getPrimaryRecipient() != null) {
|
||||||
|
intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId());
|
||||||
|
}
|
||||||
|
} catch (RecipientFormattingException rfe) {
|
||||||
|
Log.w(TAG, rfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intent.setDataAndType(record.getUri(), record.getContentType());
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
179
src/org/thoughtcrime/securesms/MediaOverviewActivity.java
Normal file
179
src/org/thoughtcrime/securesms/MediaOverviewActivity.java
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
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.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.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
|
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for displaying media attachments in-app
|
||||||
|
*/
|
||||||
|
public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
private final static String TAG = MediaOverviewActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public final static String MASTER_SECRET_EXTRA = "master_secret";
|
||||||
|
public final static String RECIPIENT_EXTRA = "recipient";
|
||||||
|
public final static String THREAD_ID_EXTRA = "thread_id";
|
||||||
|
|
||||||
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
private MasterSecret masterSecret;
|
||||||
|
|
||||||
|
private RecyclerView gridView;
|
||||||
|
private GridLayoutManager gridManager;
|
||||||
|
private TextView noImages;
|
||||||
|
private Recipient recipient;
|
||||||
|
private long threadId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle bundle) {
|
||||||
|
this.setTheme(R.style.TextSecure_DarkTheme);
|
||||||
|
dynamicLanguage.onCreate(this);
|
||||||
|
|
||||||
|
super.onCreate(bundle);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
dynamicLanguage.onResume(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeActionBar() {
|
||||||
|
getSupportActionBar().setTitle(recipient == null
|
||||||
|
? getString(R.string.AndroidManifest__media_overview)
|
||||||
|
: getString(R.string.AndroidManifest__media_overview_named, recipient.toShortString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeResources() {
|
||||||
|
masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
|
||||||
|
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
final long recipientId = getIntent().getLongExtra(RECIPIENT_EXTRA, -1);
|
||||||
|
if (recipientId > -1) {
|
||||||
|
recipient = RecipientFactory.getRecipientForId(this, recipientId, true);
|
||||||
|
recipient.addListener(new RecipientModifiedListener() {
|
||||||
|
@Override
|
||||||
|
public void onModified(Recipient recipient) {
|
||||||
|
initializeActionBar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
recipient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: finish(); return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return new ThreadMediaLoader(this, threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
Log.w(TAG, "onLoadFinished()");
|
||||||
|
gridView.setAdapter(new ImageMediaAdapter(this, masterSecret, cursor));
|
||||||
|
noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||||
|
((CursorRecyclerViewAdapter)gridView.getAdapter()).changeCursor(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ThreadMediaLoader extends AbstractCursorLoader {
|
||||||
|
private final long threadId;
|
||||||
|
|
||||||
|
public ThreadMediaLoader(Context context, long threadId) {
|
||||||
|
super(context);
|
||||||
|
this.threadId = threadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor getCursor() {
|
||||||
|
return DatabaseFactory.getPartDatabase(getContext()).getImagesForThread(threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
public class SquareLinearLayout extends LinearLayout {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public SquareLinearLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public SquareLinearLayout(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused")
|
||||||
|
public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("unused")
|
||||||
|
public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView.Adapter that manages a Cursor, comparable to the CursorAdapter usable in ListView/GridView.
|
||||||
|
*/
|
||||||
|
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
|
||||||
|
private final Context context;
|
||||||
|
private final DataSetObserver observer = new AdapterDataSetObserver();
|
||||||
|
|
||||||
|
private Cursor cursor;
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
protected CursorRecyclerViewAdapter(Context context, Cursor cursor) {
|
||||||
|
this.context = context;
|
||||||
|
this.cursor = cursor;
|
||||||
|
if (cursor != null) {
|
||||||
|
valid = true;
|
||||||
|
cursor.registerDataSetObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasStableIds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor getCursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeCursor(Cursor cursor) {
|
||||||
|
Cursor old = swapCursor(cursor);
|
||||||
|
if (old != null) {
|
||||||
|
old.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
|
if (newCursor == cursor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cursor oldCursor = cursor;
|
||||||
|
if (oldCursor != null) {
|
||||||
|
oldCursor.unregisterDataSetObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = newCursor;
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.registerDataSetObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = cursor != null;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
return oldCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return isActiveCursor() ? cursor.getCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return isActiveCursor() && cursor.moveToPosition(position)
|
||||||
|
? cursor.getLong(cursor.getColumnIndexOrThrow("_id"))
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(VH viewHolder, int position) {
|
||||||
|
if (!isActiveCursor()) {
|
||||||
|
throw new IllegalStateException("this should only be called when the cursor is valid");
|
||||||
|
}
|
||||||
|
if (!cursor.moveToPosition(position)) {
|
||||||
|
throw new IllegalStateException("couldn't move cursor to position " + position);
|
||||||
|
}
|
||||||
|
onBindViewHolder(viewHolder, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isActiveCursor() {
|
||||||
|
return valid && cursor != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AdapterDataSetObserver extends DataSetObserver {
|
||||||
|
@Override
|
||||||
|
public void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
valid = true;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidated() {
|
||||||
|
super.onInvalidated();
|
||||||
|
valid = false;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,12 +23,14 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||||
@ -91,6 +93,20 @@ public class PartDatabase extends Database {
|
|||||||
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");",
|
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ID + ", "
|
||||||
|
+ TABLE_NAME + "." + CONTENT_TYPE + ", "
|
||||||
|
+ TABLE_NAME + "." + ASPECT_RATIO + ", "
|
||||||
|
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.NORMALIZED_DATE_RECEIVED + ", "
|
||||||
|
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.ADDRESS + " "
|
||||||
|
+ "FROM " + TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME
|
||||||
|
+ " ON " + TABLE_NAME + "." + MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " "
|
||||||
|
+ "WHERE " + MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
|
||||||
|
+ " FROM " + MmsDatabase.TABLE_NAME
|
||||||
|
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND "
|
||||||
|
+ CONTENT_TYPE + " LIKE 'image/%' "
|
||||||
|
+ "ORDER BY " + TABLE_NAME + "." + ID + " DESC";
|
||||||
|
|
||||||
|
|
||||||
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
|
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
|
||||||
|
|
||||||
public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||||
@ -135,6 +151,13 @@ public class PartDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cursor getImagesForThread(long threadId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
|
Cursor cursor = database.rawQuery(IMAGES_QUERY, new String[]{threadId+""});
|
||||||
|
setNotifyConverationListeners(cursor, threadId);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Pair<Long, PduPart>> getParts(long mmsId) {
|
public List<Pair<Long, PduPart>> getParts(long mmsId) {
|
||||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
List<Pair<Long, PduPart>> results = new LinkedList<>();
|
List<Pair<Long, PduPart>> results = new LinkedList<>();
|
||||||
@ -509,6 +532,47 @@ public class PartDatabase extends Database {
|
|||||||
database.update(TABLE_NAME, values, ID_WHERE, new String[]{partId+""});
|
database.update(TABLE_NAME, values, ID_WHERE, new String[]{partId+""});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ImageRecord {
|
||||||
|
private long partId;
|
||||||
|
private String contentType;
|
||||||
|
private String address;
|
||||||
|
private long date;
|
||||||
|
|
||||||
|
private ImageRecord(long partId, String contentType, String address, long date) {
|
||||||
|
this.partId = partId;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.address = address;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageRecord from(Cursor cursor) {
|
||||||
|
return new ImageRecord(cursor.getLong(cursor.getColumnIndexOrThrow(ID)),
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)),
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPartId() {
|
||||||
|
return partId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri() {
|
||||||
|
return ContentUris.withAppendedId(PartAuthority.PART_CONTENT_URI, getPartId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting class ThumbnailFetchCallable implements Callable<InputStream> {
|
@VisibleForTesting class ThumbnailFetchCallable implements Callable<InputStream> {
|
||||||
private final MasterSecret masterSecret;
|
private final MasterSecret masterSecret;
|
||||||
private final long partId;
|
private final long partId;
|
||||||
|
@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.mms;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.util.SmilUtil;
|
import org.thoughtcrime.securesms.util.SmilUtil;
|
||||||
import org.w3c.dom.smil.SMILDocument;
|
import org.w3c.dom.smil.SMILDocument;
|
||||||
import org.w3c.dom.smil.SMILMediaElement;
|
import org.w3c.dom.smil.SMILMediaElement;
|
||||||
@ -34,14 +35,14 @@ import android.provider.MediaStore.Audio;
|
|||||||
|
|
||||||
public class AudioSlide extends Slide {
|
public class AudioSlide extends Slide {
|
||||||
|
|
||||||
public AudioSlide(Context context, PduPart part) {
|
|
||||||
super(context, part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AudioSlide(Context context, Uri uri) throws IOException, MediaTooLargeException {
|
public AudioSlide(Context context, Uri uri) throws IOException, MediaTooLargeException {
|
||||||
super(context, constructPartFromUri(context, uri));
|
super(context, constructPartFromUri(context, uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AudioSlide(Context context, MasterSecret masterSecret, PduPart part) {
|
||||||
|
super(context, masterSecret, part);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasImage() {
|
public boolean hasImage() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -112,6 +112,11 @@ public class ImageSlide extends Slide {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThumbnailOn(ImageView imageView) {
|
public void setThumbnailOn(ImageView imageView) {
|
||||||
|
setThumbnailOn(imageView, imageView.getWidth(), imageView.getHeight(), new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThumbnailOn(ImageView imageView, final int width, final int height, final Drawable placeholder) {
|
||||||
Drawable thumbnail = getCachedThumbnail();
|
Drawable thumbnail = getCachedThumbnail();
|
||||||
|
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null) {
|
||||||
@ -120,24 +125,22 @@ public class ImageSlide extends Slide {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ColorDrawable temporaryDrawable = new ColorDrawable(Color.TRANSPARENT);
|
final WeakReference<ImageView> weakImageView = new WeakReference<>(imageView);
|
||||||
final WeakReference<ImageView> weakImageView = new WeakReference<ImageView>(imageView);
|
|
||||||
final Handler handler = new Handler();
|
final Handler handler = new Handler();
|
||||||
final int maxWidth = imageView.getWidth();
|
|
||||||
final int maxHeight = imageView.getHeight();
|
|
||||||
|
|
||||||
imageView.setImageDrawable(temporaryDrawable);
|
imageView.setImageDrawable(placeholder);
|
||||||
|
|
||||||
if (maxWidth == 0 || maxHeight == 0)
|
if (width == 0 || height == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MmsDatabase.slideResolver.execute(new Runnable() {
|
MmsDatabase.slideResolver.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final Drawable bitmap = getThumbnail(maxWidth, maxHeight);
|
final Drawable bitmap = getThumbnail(width, height);
|
||||||
final ImageView destination = weakImageView.get();
|
final ImageView destination = weakImageView.get();
|
||||||
|
|
||||||
if (destination != null && destination.getDrawable() == temporaryDrawable) {
|
Log.w(TAG, "slide resolved, destination available? " + (destination == null));
|
||||||
|
if (destination != null && destination.getDrawable() == placeholder) {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -156,7 +159,7 @@ public class ImageSlide extends Slide {
|
|||||||
imageView.setImageDrawable(thumbnail);
|
imageView.setImageDrawable(thumbnail);
|
||||||
((AnimationDrawable)imageView.getDrawable()).start();
|
((AnimationDrawable)imageView.getDrawable()).start();
|
||||||
} else {
|
} else {
|
||||||
TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), thumbnail});
|
TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{imageView.getDrawable(), thumbnail});
|
||||||
imageView.setImageDrawable(fadingResult);
|
imageView.setImageDrawable(fadingResult);
|
||||||
fadingResult.startTransition(300);
|
fadingResult.startTransition(300);
|
||||||
}
|
}
|
||||||
|
@ -10,27 +10,21 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class PartAuthority {
|
public class PartAuthority {
|
||||||
|
|
||||||
private static final String PART_URI_STRING = "content://org.thoughtcrime.securesms/part";
|
private static final String PART_URI_STRING = "content://org.thoughtcrime.securesms/part";
|
||||||
private static final String THUMB_URI_STRING = "content://org.thoughtcrime.securesms/thumb";
|
|
||||||
|
|
||||||
public static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
public static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||||
public static final Uri THUMB_CONTENT_URI = Uri.parse(THUMB_URI_STRING);
|
|
||||||
|
|
||||||
private static final int PART_ROW = 1;
|
private static final int PART_ROW = 1;
|
||||||
private static final int THUMB_ROW = 2;
|
|
||||||
|
|
||||||
private static final UriMatcher uriMatcher;
|
private static final UriMatcher uriMatcher;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
uriMatcher.addURI("org.thoughtcrime.securesms", "part/#", PART_ROW);
|
uriMatcher.addURI("org.thoughtcrime.securesms", "part/#", PART_ROW);
|
||||||
uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/#", THUMB_ROW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputStream getPartStream(Context context, MasterSecret masterSecret, Uri uri)
|
public static InputStream getPartStream(Context context, MasterSecret masterSecret, Uri uri)
|
||||||
@ -42,7 +36,6 @@ public class PartAuthority {
|
|||||||
try {
|
try {
|
||||||
switch (match) {
|
switch (match) {
|
||||||
case PART_ROW: return partDatabase.getPartStream(masterSecret, ContentUris.parseId(uri));
|
case PART_ROW: return partDatabase.getPartStream(masterSecret, ContentUris.parseId(uri));
|
||||||
case THUMB_ROW: return partDatabase.getThumbnailStream(masterSecret, ContentUris.parseId(uri));
|
|
||||||
default: return context.getContentResolver().openInputStream(uri);
|
default: return context.getContentResolver().openInputStream(uri);
|
||||||
}
|
}
|
||||||
} catch (SecurityException se) {
|
} catch (SecurityException se) {
|
||||||
|
@ -78,6 +78,10 @@ public abstract class Slide {
|
|||||||
imageView.setImageDrawable(getThumbnail(imageView.getWidth(), imageView.getHeight()));
|
imageView.setImageDrawable(getThumbnail(imageView.getWidth(), imageView.getHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setThumbnailOn(ImageView imageView, int height, int width, Drawable placeholder) {
|
||||||
|
imageView.setImageDrawable(getThumbnail(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
public Bitmap getGeneratedThumbnail() { return null; }
|
public Bitmap getGeneratedThumbnail() { return null; }
|
||||||
|
|
||||||
public boolean hasImage() {
|
public boolean hasImage() {
|
||||||
|
@ -20,7 +20,9 @@ import android.content.Context;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
|
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.SmilUtil;
|
import org.thoughtcrime.securesms.util.SmilUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@ -41,20 +43,10 @@ public class SlideDeck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SlideDeck(Context context, MasterSecret masterSecret, PduBody body) {
|
public SlideDeck(Context context, MasterSecret masterSecret, PduBody body) {
|
||||||
try {
|
|
||||||
for (int i=0;i<body.getPartsNum();i++) {
|
for (int i=0;i<body.getPartsNum();i++) {
|
||||||
String contentType = new String(body.getPart(i).getContentType(), CharacterSets.MIMENAME_ISO_8859_1);
|
String contentType = Util.toIsoString(body.getPart(i).getContentType());
|
||||||
if (ContentType.isImageType(contentType))
|
Slide slide = MediaUtil.getSlideForPart(context, masterSecret, body.getPart(i), contentType);
|
||||||
slides.add(new ImageSlide(context, masterSecret, body.getPart(i)));
|
if (slide != null) slides.add(slide);
|
||||||
else if (ContentType.isVideoType(contentType))
|
|
||||||
slides.add(new VideoSlide(context, body.getPart(i)));
|
|
||||||
else if (ContentType.isAudioType(contentType))
|
|
||||||
slides.add(new AudioSlide(context, body.getPart(i)));
|
|
||||||
else if (ContentType.isTextType(contentType))
|
|
||||||
slides.add(new TextSlide(context, masterSecret, body.getPart(i)));
|
|
||||||
}
|
|
||||||
} catch (UnsupportedEncodingException uee) {
|
|
||||||
throw new AssertionError(uee);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.mms;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.util.SmilUtil;
|
import org.thoughtcrime.securesms.util.SmilUtil;
|
||||||
import org.w3c.dom.smil.SMILDocument;
|
import org.w3c.dom.smil.SMILDocument;
|
||||||
import org.w3c.dom.smil.SMILMediaElement;
|
import org.w3c.dom.smil.SMILMediaElement;
|
||||||
@ -35,14 +36,14 @@ import android.util.Log;
|
|||||||
|
|
||||||
public class VideoSlide extends Slide {
|
public class VideoSlide extends Slide {
|
||||||
|
|
||||||
public VideoSlide(Context context, PduPart part) {
|
|
||||||
super(context, part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VideoSlide(Context context, Uri uri) throws IOException, MediaTooLargeException {
|
public VideoSlide(Context context, Uri uri) throws IOException, MediaTooLargeException {
|
||||||
super(context, constructPartFromUri(context, uri));
|
super(context, constructPartFromUri(context, uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VideoSlide(Context context, MasterSecret masterSecret, PduPart part) {
|
||||||
|
super(context, masterSecret, part);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable getThumbnail(int width, int height) {
|
public Drawable getThumbnail(int width, int height) {
|
||||||
return context.getResources().getDrawable(R.drawable.ic_launcher_video_player);
|
return context.getResources().getDrawable(R.drawable.ic_launcher_video_player);
|
||||||
|
@ -9,8 +9,13 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||||
|
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||||
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
|
import org.thoughtcrime.securesms.mms.MediaTooLargeException;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
|
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -62,6 +67,19 @@ public class MediaUtil {
|
|||||||
return BitmapUtil.createScaledBitmap(context, masterSecret, uri, maxSize, maxSize);
|
return BitmapUtil.createScaledBitmap(context, masterSecret, uri, maxSize, maxSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Slide getSlideForPart(Context context, MasterSecret masterSecret, PduPart part, String contentType) {
|
||||||
|
Slide slide = null;
|
||||||
|
if (ContentType.isImageType(contentType)) {
|
||||||
|
slide = new ImageSlide(context, masterSecret, part);
|
||||||
|
} else if (ContentType.isVideoType(contentType)) {
|
||||||
|
slide = new VideoSlide(context, masterSecret, part);
|
||||||
|
} else if (ContentType.isAudioType(contentType)) {
|
||||||
|
slide = new AudioSlide(context, masterSecret, part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slide;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isImage(PduPart part) {
|
public static boolean isImage(PduPart part) {
|
||||||
return ContentType.isImageType(Util.toIsoString(part.getContentType()));
|
return ContentType.isImageType(Util.toIsoString(part.getContentType()));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user