mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
WIP: clean up signal protocols (stickers)
This commit is contained in:
parent
04f140ee09
commit
19a829d011
@ -62,7 +62,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
|
|||||||
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
||||||
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
|
@ -36,6 +36,5 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
void onQuoteClicked(MmsMessageRecord messageRecord);
|
void onQuoteClicked(MmsMessageRecord messageRecord);
|
||||||
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
|
||||||
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
|
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
|
||||||
void onStickerClicked(@NonNull StickerLocator stickerLocator);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,12 +86,6 @@ object FullBackupExporter {
|
|||||||
},
|
},
|
||||||
count)
|
count)
|
||||||
}
|
}
|
||||||
StickerDatabase.TABLE_NAME -> {
|
|
||||||
exportTable(table, input, outputStream,
|
|
||||||
{ true },
|
|
||||||
{ cursor: Cursor -> exportSticker(attachmentSecret, cursor, outputStream) },
|
|
||||||
count)
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
exportTable(table, input, outputStream, null, null, count)
|
exportTable(table, input, outputStream, null, null, count)
|
||||||
}
|
}
|
||||||
@ -236,20 +230,6 @@ object FullBackupExporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exportSticker(attachmentSecret: AttachmentSecret, cursor: Cursor, outputStream: BackupFrameOutputStream) {
|
|
||||||
try {
|
|
||||||
val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID))
|
|
||||||
val size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH))
|
|
||||||
val data = cursor.getString(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_PATH))
|
|
||||||
val random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM))
|
|
||||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
|
||||||
ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0).use { inputStream -> outputStream.writeSticker(rowId, inputStream, size) }
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.w(TAG, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun calculateVeryOldStreamLength(attachmentSecret: AttachmentSecret, random: ByteArray?, data: String): Long {
|
private fun calculateVeryOldStreamLength(attachmentSecret: AttachmentSecret, random: ByteArray?, data: String): Long {
|
||||||
var result: Long = 0
|
var result: Long = 0
|
||||||
|
@ -67,7 +67,6 @@ object FullBackupImporter {
|
|||||||
frame.hasStatement() -> processStatement(db, frame.statement)
|
frame.hasStatement() -> processStatement(db, frame.statement)
|
||||||
frame.hasPreference() -> processPreference(context, frame.preference)
|
frame.hasPreference() -> processPreference(context, frame.preference)
|
||||||
frame.hasAttachment() -> processAttachment(context, attachmentSecret, db, frame.attachment, inputStream)
|
frame.hasAttachment() -> processAttachment(context, attachmentSecret, db, frame.attachment, inputStream)
|
||||||
frame.hasSticker() -> processSticker(context, attachmentSecret, db, frame.sticker, inputStream)
|
|
||||||
frame.hasAvatar() -> processAvatar(context, frame.avatar, inputStream)
|
frame.hasAvatar() -> processAvatar(context, frame.avatar, inputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,21 +131,6 @@ object FullBackupImporter {
|
|||||||
arrayOf(attachment.rowId.toString(), attachment.attachmentId.toString()))
|
arrayOf(attachment.rowId.toString(), attachment.attachmentId.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun processSticker(context: Context, attachmentSecret: AttachmentSecret,
|
|
||||||
db: SQLiteDatabase, sticker: Sticker,
|
|
||||||
inputStream: BackupRecordInputStream) {
|
|
||||||
val stickerDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE)
|
|
||||||
val dataFile = File.createTempFile("sticker", ".mms", stickerDirectory)
|
|
||||||
val output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false)
|
|
||||||
inputStream.readAttachmentTo(output.second, sticker.length)
|
|
||||||
val contentValues = ContentValues()
|
|
||||||
contentValues.put(StickerDatabase.FILE_PATH, dataFile.absolutePath)
|
|
||||||
contentValues.put(StickerDatabase.FILE_RANDOM, output.first)
|
|
||||||
db.update(StickerDatabase.TABLE_NAME, contentValues,
|
|
||||||
StickerDatabase._ID + " = ?", arrayOf(sticker.rowId.toString()))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun processAvatar(context: Context, avatar: Avatar, inputStream: BackupRecordInputStream) {
|
private fun processAvatar(context: Context, avatar: Avatar, inputStream: BackupRecordInputStream) {
|
||||||
inputStream.readAttachmentTo(FileOutputStream(
|
inputStream.readAttachmentTo(FileOutputStream(
|
||||||
|
@ -7,8 +7,6 @@ import android.util.AttributeSet;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.AppCompatImageButton;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
@ -82,8 +80,5 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
|
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
|
||||||
setStickerMode(provider instanceof StickerKeyboardProvider);
|
|
||||||
TextSecurePreferences.setMediaKeyboardMode(getContext(), (provider instanceof StickerKeyboardProvider) ? TextSecurePreferences.MediaKeyboardMode.STICKER
|
|
||||||
: TextSecurePreferences.MediaKeyboardMode.EMOJI);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,10 +182,6 @@ import org.thoughtcrime.securesms.sms.MessageSender;
|
|||||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerManagementActivity;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@ -205,7 +201,6 @@ import org.session.libsession.utilities.concurrent.AssertedSuccessListener;
|
|||||||
import org.session.libsignal.utilities.concurrent.ListenableFuture;
|
import org.session.libsignal.utilities.concurrent.ListenableFuture;
|
||||||
import org.session.libsignal.utilities.concurrent.SettableFuture;
|
import org.session.libsignal.utilities.concurrent.SettableFuture;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.MediaKeyboardMode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -239,7 +234,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
InputPanel.MediaListener,
|
InputPanel.MediaListener,
|
||||||
ComposeText.CursorPositionChangedListener,
|
ComposeText.CursorPositionChangedListener,
|
||||||
ConversationSearchBottomBar.EventListener,
|
ConversationSearchBottomBar.EventListener,
|
||||||
StickerKeyboardProvider.StickerEventListener,
|
|
||||||
LokiThreadDatabaseDelegate
|
LokiThreadDatabaseDelegate
|
||||||
{
|
{
|
||||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||||
@ -303,7 +297,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
private LinkPreviewViewModel linkPreviewViewModel;
|
private LinkPreviewViewModel linkPreviewViewModel;
|
||||||
private ConversationSearchViewModel searchViewModel;
|
private ConversationSearchViewModel searchViewModel;
|
||||||
private ConversationStickerViewModel stickerViewModel;
|
|
||||||
|
|
||||||
private Recipient recipient;
|
private Recipient recipient;
|
||||||
private long threadId;
|
private long threadId;
|
||||||
@ -361,7 +354,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
initializeResources();
|
initializeResources();
|
||||||
initializeLinkPreviewObserver();
|
initializeLinkPreviewObserver();
|
||||||
initializeSearchObserver();
|
initializeSearchObserver();
|
||||||
initializeStickerObserver();
|
|
||||||
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Boolean result) {
|
public void onSuccess(Boolean result) {
|
||||||
@ -1513,52 +1505,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeStickerObserver() {
|
|
||||||
StickerSearchRepository repository = new StickerSearchRepository(this);
|
|
||||||
|
|
||||||
stickerViewModel = ViewModelProviders.of(this, new ConversationStickerViewModel.Factory(getApplication(), repository))
|
|
||||||
.get(ConversationStickerViewModel.class);
|
|
||||||
|
|
||||||
stickerViewModel.getStickerResults().observe(this, stickers -> {
|
|
||||||
if (stickers == null) return;
|
|
||||||
|
|
||||||
inputPanel.setStickerSuggestions(stickers);
|
|
||||||
});
|
|
||||||
|
|
||||||
stickerViewModel.getStickersAvailability().observe(this, stickersAvailable -> {
|
|
||||||
if (stickersAvailable == null) return;
|
|
||||||
|
|
||||||
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
|
|
||||||
MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this);
|
|
||||||
boolean stickerIntro = !TextSecurePreferences.hasSeenStickerIntroTooltip(this);
|
|
||||||
|
|
||||||
if (stickersAvailable) {
|
|
||||||
inputPanel.showMediaKeyboardToggle(true);
|
|
||||||
inputPanel.setMediaKeyboardToggleMode(isSystemEmojiPreferred || keyboardMode == MediaKeyboardMode.STICKER);
|
|
||||||
if (stickerIntro) showStickerIntroductionTooltip();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emojiDrawerStub.resolved()) {
|
|
||||||
initializeMediaKeyboardProviders(emojiDrawerStub.get(), stickersAvailable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showStickerIntroductionTooltip() {
|
|
||||||
TextSecurePreferences.setMediaKeyboardMode(this, MediaKeyboardMode.STICKER);
|
|
||||||
inputPanel.setMediaKeyboardToggleMode(true);
|
|
||||||
|
|
||||||
TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView())
|
|
||||||
.setBackgroundTint(getResources().getColor(R.color.core_blue))
|
|
||||||
.setTextColor(getResources().getColor(R.color.core_white))
|
|
||||||
.setText(R.string.ConversationActivity_new_say_it_with_stickers)
|
|
||||||
.setOnDismissListener(() -> {
|
|
||||||
TextSecurePreferences.setHasSeenStickerIntroTooltip(this, true);
|
|
||||||
EventBus.getDefault().removeStickyEvent(StickerPackInstallEvent.class);
|
|
||||||
})
|
|
||||||
.show(TooltipPopup.POSITION_ABOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchMoveUpPressed() {
|
public void onSearchMoveUpPressed() {
|
||||||
searchViewModel.onMoveUp();
|
searchViewModel.onMoveUp();
|
||||||
@ -1588,17 +1534,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
|
||||||
public void onStickerPackInstalled(final StickerPackInstallEvent event) {
|
|
||||||
if (!TextSecurePreferences.hasSeenStickerIntroTooltip(this)) return;
|
|
||||||
|
|
||||||
EventBus.getDefault().removeStickyEvent(event);
|
|
||||||
TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView())
|
|
||||||
.setText(R.string.ConversationActivity_sticker_pack_installed)
|
|
||||||
.setIconGlideModel(event.getIconGlideModel())
|
|
||||||
.show(TooltipPopup.POSITION_ABOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) {
|
public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) {
|
||||||
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||||
@ -1813,21 +1748,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
|
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) {
|
||||||
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
|
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
|
||||||
|
if (!isSystemEmojiPreferred) {
|
||||||
if (stickersAvailable) {
|
|
||||||
if (isSystemEmojiPreferred) {
|
|
||||||
mediaKeyboard.setProviders(0, new StickerKeyboardProvider(this, this));
|
|
||||||
} else {
|
|
||||||
MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this);
|
|
||||||
int index = keyboardMode == MediaKeyboardMode.STICKER ? 1 : 0;
|
|
||||||
|
|
||||||
mediaKeyboard.setProviders(index,
|
|
||||||
new EmojiKeyboardProvider(this, inputPanel),
|
|
||||||
new StickerKeyboardProvider(this, this));
|
|
||||||
}
|
|
||||||
} else if (!isSystemEmojiPreferred) {
|
|
||||||
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel));
|
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2230,9 +2153,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onEmojiToggle() {
|
public void onEmojiToggle() {
|
||||||
if (!emojiDrawerStub.resolved()) {
|
if (!emojiDrawerStub.resolved()) {
|
||||||
Boolean stickersAvailable = stickerViewModel.getStickersAvailability().getValue();
|
initializeMediaKeyboardProviders(emojiDrawerStub.get());
|
||||||
|
|
||||||
initializeMediaKeyboardProviders(emojiDrawerStub.get(), stickersAvailable == null ? false : stickersAvailable);
|
|
||||||
|
|
||||||
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
|
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
|
||||||
}
|
}
|
||||||
@ -2272,23 +2193,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end);
|
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerSelected(@NonNull StickerRecord stickerRecord) {
|
|
||||||
sendSticker(stickerRecord, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerManagementClicked() {
|
|
||||||
startActivity(StickerManagementActivity.getIntent(this));
|
|
||||||
container.hideAttachedInput(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
|
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
|
||||||
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
|
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
|
||||||
|
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
|
||||||
DatabaseFactory.getStickerDatabase(this).updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {
|
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {
|
||||||
@ -2404,12 +2310,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
|
||||||
if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) {
|
if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) {
|
||||||
composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50);
|
composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
stickerViewModel.onInputTextUpdated(s.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -85,7 +85,6 @@ import org.thoughtcrime.securesms.profiles.UnknownSenderView;
|
|||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
@ -1084,13 +1083,6 @@ public class ConversationFragment extends Fragment
|
|||||||
startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms));
|
startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerClicked(@NonNull StickerLocator sticker) {
|
|
||||||
if (getContext() != null && getActivity() != null) {
|
|
||||||
startActivity(StickerPackPreviewActivity.getIntent(sticker.getPackId(), sticker.getPackKey()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ActionModeCallback implements ActionMode.Callback {
|
private class ActionModeCallback implements ActionMode.Callback {
|
||||||
|
@ -96,7 +96,6 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
|||||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||||
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
|
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||||
@ -460,8 +459,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width);
|
int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width);
|
||||||
|
|
||||||
return linkPreview.getThumbnail().isPresent() &&
|
return linkPreview.getThumbnail().isPresent() &&
|
||||||
linkPreview.getThumbnail().get().getWidth() >= minWidth &&
|
linkPreview.getThumbnail().get().getWidth() >= minWidth;
|
||||||
!StickerUrl.isValidShareLink(linkPreview.getUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) {
|
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) {
|
||||||
@ -1154,9 +1152,6 @@ public class ConversationItem extends LinearLayout
|
|||||||
public void onClick(View v, Slide slide) {
|
public void onClick(View v, Slide slide) {
|
||||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||||
performClick();
|
performClick();
|
||||||
} else if (eventListener != null && hasSticker(messageRecord)){
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
eventListener.onStickerClicked(((MmsMessageRecord) messageRecord).getSlideDeck().getStickerSlide().asAttachment().getSticker());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.conversation;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.CursorList;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
|
||||||
import org.thoughtcrime.securesms.util.CloseableLiveData;
|
|
||||||
import org.session.libsession.utilities.Throttler;
|
|
||||||
|
|
||||||
class ConversationStickerViewModel extends ViewModel {
|
|
||||||
|
|
||||||
private static final int SEARCH_LIMIT = 10;
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final StickerSearchRepository repository;
|
|
||||||
private final CloseableLiveData<CursorList<StickerRecord>> stickers;
|
|
||||||
private final MutableLiveData<Boolean> stickersAvailable;
|
|
||||||
private final Throttler availabilityThrottler;
|
|
||||||
private final ContentObserver packObserver;
|
|
||||||
|
|
||||||
private ConversationStickerViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
this.stickers = new CloseableLiveData<>();
|
|
||||||
this.stickersAvailable = new MutableLiveData<>();
|
|
||||||
this.availabilityThrottler = new Throttler(500);
|
|
||||||
this.packObserver = new ContentObserver(new Handler()) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull LiveData<CursorList<StickerRecord>> getStickerResults() {
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull LiveData<Boolean> getStickersAvailability() {
|
|
||||||
repository.getStickerFeatureAvailability(stickersAvailable::postValue);
|
|
||||||
return stickersAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onInputTextUpdated(@NonNull String text) {
|
|
||||||
if (TextUtils.isEmpty(text) || text.length() > SEARCH_LIMIT) {
|
|
||||||
stickers.setValue(CursorList.emptyList());
|
|
||||||
} else {
|
|
||||||
repository.searchByEmoji(text, stickers::postValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
stickers.close();
|
|
||||||
application.getContentResolver().unregisterContentObserver(packObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
|
||||||
private final Application application;
|
|
||||||
private final StickerSearchRepository repository;
|
|
||||||
|
|
||||||
public Factory(@NonNull Application application, @NonNull StickerSearchRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return modelClass.cast(new ConversationStickerViewModel(application, repository));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -548,18 +548,6 @@ public class AttachmentDatabase extends Database {
|
|||||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns (pack_id, pack_key) pairs that are referenced in attachments but not in the stickers
|
|
||||||
* database.
|
|
||||||
*/
|
|
||||||
public @Nullable Cursor getUnavailableStickerPacks() {
|
|
||||||
String query = "SELECT DISTINCT " + STICKER_PACK_ID + ", " + STICKER_PACK_KEY + " FROM " + TABLE_NAME + " WHERE " + STICKER_PACK_ID + " NOT IN (" +
|
|
||||||
"SELECT DISTINCT " + StickerDatabase.PACK_ID + " FROM " + StickerDatabase.TABLE_NAME +
|
|
||||||
")";
|
|
||||||
|
|
||||||
return databaseHelper.getReadableDatabase().rawQuery(query, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasStickerAttachments() {
|
public boolean hasStickerAttachments() {
|
||||||
String selection = STICKER_PACK_ID + " NOT NULL";
|
String selection = STICKER_PACK_ID + " NOT NULL";
|
||||||
|
|
||||||
|
@ -55,7 +55,6 @@ public class DatabaseFactory {
|
|||||||
private final GroupReceiptDatabase groupReceiptDatabase;
|
private final GroupReceiptDatabase groupReceiptDatabase;
|
||||||
private final SearchDatabase searchDatabase;
|
private final SearchDatabase searchDatabase;
|
||||||
private final JobDatabase jobDatabase;
|
private final JobDatabase jobDatabase;
|
||||||
private final StickerDatabase stickerDatabase;
|
|
||||||
|
|
||||||
// Loki
|
// Loki
|
||||||
private final LokiAPIDatabase lokiAPIDatabase;
|
private final LokiAPIDatabase lokiAPIDatabase;
|
||||||
@ -130,10 +129,6 @@ public class DatabaseFactory {
|
|||||||
return getInstance(context).jobDatabase;
|
return getInstance(context).jobDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StickerDatabase getStickerDatabase(Context context) {
|
|
||||||
return getInstance(context).stickerDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SQLiteDatabase getBackupDatabase(Context context) {
|
public static SQLiteDatabase getBackupDatabase(Context context) {
|
||||||
return getInstance(context).databaseHelper.getReadableDatabase();
|
return getInstance(context).databaseHelper.getReadableDatabase();
|
||||||
}
|
}
|
||||||
@ -199,7 +194,6 @@ public class DatabaseFactory {
|
|||||||
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
||||||
this.searchDatabase = new SearchDatabase(context, databaseHelper);
|
this.searchDatabase = new SearchDatabase(context, databaseHelper);
|
||||||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
|
||||||
this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper);
|
this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper);
|
||||||
this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper);
|
this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper);
|
||||||
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
|
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
|
||||||
|
@ -1,481 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
|
||||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
|
||||||
import org.thoughtcrime.securesms.database.model.IncomingSticker;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
|
||||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
|
|
||||||
import org.session.libsession.utilities.Util;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class StickerDatabase extends Database {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerDatabase.class);
|
|
||||||
|
|
||||||
public static final String TABLE_NAME = "sticker";
|
|
||||||
public static final String _ID = "_id";
|
|
||||||
static final String PACK_ID = "pack_id";
|
|
||||||
private static final String PACK_KEY = "pack_key";
|
|
||||||
private static final String PACK_TITLE = "pack_title";
|
|
||||||
private static final String PACK_AUTHOR = "pack_author";
|
|
||||||
private static final String STICKER_ID = "sticker_id";
|
|
||||||
private static final String EMOJI = "emoji";
|
|
||||||
private static final String COVER = "cover";
|
|
||||||
private static final String INSTALLED = "installed";
|
|
||||||
private static final String LAST_USED = "last_used";
|
|
||||||
public static final String FILE_PATH = "file_path";
|
|
||||||
public static final String FILE_LENGTH = "file_length";
|
|
||||||
public static final String FILE_RANDOM = "file_random";
|
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
PACK_ID + " TEXT NOT NULL, " +
|
|
||||||
PACK_KEY + " TEXT NOT NULL, " +
|
|
||||||
PACK_TITLE + " TEXT NOT NULL, " +
|
|
||||||
PACK_AUTHOR + " TEXT NOT NULL, " +
|
|
||||||
STICKER_ID + " INTEGER, " +
|
|
||||||
COVER + " INTEGER, " +
|
|
||||||
EMOJI + " TEXT NOT NULL, " +
|
|
||||||
LAST_USED + " INTEGER, " +
|
|
||||||
INSTALLED + " INTEGER," +
|
|
||||||
FILE_PATH + " TEXT NOT NULL, " +
|
|
||||||
FILE_LENGTH + " INTEGER, " +
|
|
||||||
FILE_RANDOM + " BLOB, " +
|
|
||||||
"UNIQUE(" + PACK_ID + ", " + STICKER_ID + ", " + COVER + ") ON CONFLICT IGNORE)";
|
|
||||||
|
|
||||||
public static final String[] CREATE_INDEXES = {
|
|
||||||
"CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON " + TABLE_NAME + " (" + PACK_ID + ");",
|
|
||||||
"CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON " + TABLE_NAME + " (" + STICKER_ID + ");"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final String DIRECTORY = "stickers";
|
|
||||||
|
|
||||||
private final AttachmentSecret attachmentSecret;
|
|
||||||
|
|
||||||
public StickerDatabase(Context context, SQLCipherOpenHelper databaseHelper, AttachmentSecret attachmentSecret) {
|
|
||||||
super(context, databaseHelper);
|
|
||||||
this.attachmentSecret = attachmentSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream) throws IOException {
|
|
||||||
FileInfo fileInfo = saveStickerImage(dataStream);
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
|
|
||||||
contentValues.put(PACK_ID, sticker.getPackId());
|
|
||||||
contentValues.put(PACK_KEY, sticker.getPackKey());
|
|
||||||
contentValues.put(PACK_TITLE, sticker.getPackTitle());
|
|
||||||
contentValues.put(PACK_AUTHOR, sticker.getPackAuthor());
|
|
||||||
contentValues.put(STICKER_ID, sticker.getStickerId());
|
|
||||||
contentValues.put(EMOJI, sticker.getEmoji());
|
|
||||||
contentValues.put(COVER, sticker.isCover() ? 1 : 0);
|
|
||||||
contentValues.put(INSTALLED, sticker.isInstalled() ? 1 : 0);
|
|
||||||
contentValues.put(FILE_PATH, fileInfo.getFile().getAbsolutePath());
|
|
||||||
contentValues.put(FILE_LENGTH, fileInfo.getLength());
|
|
||||||
contentValues.put(FILE_RANDOM, fileInfo.getRandom());
|
|
||||||
|
|
||||||
long id = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
|
|
||||||
|
|
||||||
if (id > 0) {
|
|
||||||
notifyStickerListeners();
|
|
||||||
|
|
||||||
if (sticker.isCover()) {
|
|
||||||
notifyStickerPackListeners();
|
|
||||||
|
|
||||||
if (sticker.isInstalled()) {
|
|
||||||
broadcastInstallEvent(sticker.getPackId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable StickerRecord getSticker(@NonNull String packId, int stickerId, boolean isCover) {
|
|
||||||
String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { packId, String.valueOf(stickerId), String.valueOf(isCover ? 1 : 0) };
|
|
||||||
|
|
||||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
|
|
||||||
return new StickerRecordReader(cursor).getNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable StickerPackRecord getStickerPack(@NonNull String packId) {
|
|
||||||
String query = PACK_ID + " = ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { packId, "1" };
|
|
||||||
|
|
||||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, "1")) {
|
|
||||||
return new StickerPackRecordReader(cursor).getNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getInstalledStickerPacks() {
|
|
||||||
String selection = COVER + " = ? AND " + INSTALLED + " = ?";
|
|
||||||
String[] args = new String[] { "1", "1" };
|
|
||||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
|
|
||||||
|
|
||||||
setNotifyStickerPackListeners(cursor);
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getStickersByEmoji(@NonNull String emoji) {
|
|
||||||
String selection = EMOJI + " LIKE ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { "%"+emoji+"%", "0" };
|
|
||||||
|
|
||||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
|
|
||||||
setNotifyStickerListeners(cursor);
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getAllStickerPacks() {
|
|
||||||
return getAllStickerPacks(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getAllStickerPacks(@Nullable String limit) {
|
|
||||||
String query = COVER + " = ?";
|
|
||||||
String[] args = new String[] { "1" };
|
|
||||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, limit);
|
|
||||||
setNotifyStickerPackListeners(cursor);
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getStickersForPack(@NonNull String packId) {
|
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
|
||||||
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { packId, "0" };
|
|
||||||
|
|
||||||
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null);
|
|
||||||
setNotifyStickerListeners(cursor);
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Cursor getRecentlyUsedStickers(int limit) {
|
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
|
||||||
String selection = LAST_USED + " > ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { "0", "0" };
|
|
||||||
|
|
||||||
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit));
|
|
||||||
setNotifyStickerListeners(cursor);
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable InputStream getStickerStream(long rowId) throws IOException {
|
|
||||||
String selection = _ID + " = ?";
|
|
||||||
String[] args = new String[] { String.valueOf(rowId) };
|
|
||||||
|
|
||||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null)) {
|
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
|
||||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
|
||||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(FILE_RANDOM));
|
|
||||||
|
|
||||||
if (path != null) {
|
|
||||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(path), 0);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "getStickerStream("+rowId+") - No sticker data");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "getStickerStream("+rowId+") - Sticker not found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPackInstalled(@NonNull String packId) {
|
|
||||||
StickerPackRecord record = getStickerPack(packId);
|
|
||||||
|
|
||||||
return (record != null && record.isInstalled());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPackAvailableAsReference(@NonNull String packId) {
|
|
||||||
return getStickerPack(packId) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateStickerLastUsedTime(long rowId, long lastUsed) {
|
|
||||||
String selection = _ID + " = ?";
|
|
||||||
String[] args = new String[] { String.valueOf(rowId) };
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
|
|
||||||
values.put(LAST_USED, lastUsed);
|
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, selection, args);
|
|
||||||
|
|
||||||
notifyStickerListeners();
|
|
||||||
notifyStickerPackListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markPackAsInstalled(@NonNull String packKey) {
|
|
||||||
updatePackInstalled(databaseHelper.getWritableDatabase(), packKey, true);
|
|
||||||
notifyStickerPackListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteOrphanedPacks() {
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
|
||||||
String query = "SELECT " + PACK_ID + " FROM " + TABLE_NAME + " WHERE " + INSTALLED + " = ? AND " +
|
|
||||||
PACK_ID + " NOT IN (" +
|
|
||||||
"SELECT DISTINCT " + AttachmentDatabase.STICKER_PACK_ID + " FROM " + AttachmentDatabase.TABLE_NAME + " " +
|
|
||||||
"WHERE " + AttachmentDatabase.STICKER_PACK_ID + " NOT NULL" +
|
|
||||||
")";
|
|
||||||
String[] args = new String[] { "0" };
|
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean performedDelete = false;
|
|
||||||
|
|
||||||
try (Cursor cursor = db.rawQuery(query, args)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
String packId = cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID));
|
|
||||||
|
|
||||||
if (!BlessedPacks.contains(packId)) {
|
|
||||||
deletePack(db, packId);
|
|
||||||
performedDelete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
|
|
||||||
if (performedDelete) {
|
|
||||||
notifyStickerPackListeners();
|
|
||||||
notifyStickerListeners();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void uninstallPack(@NonNull String packId) {
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
try {
|
|
||||||
|
|
||||||
updatePackInstalled(db, packId, false);
|
|
||||||
deleteStickersInPackExceptCover(db, packId);
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
notifyStickerPackListeners();
|
|
||||||
notifyStickerListeners();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed) {
|
|
||||||
StickerPackRecord existing = getStickerPack(packId);
|
|
||||||
|
|
||||||
if (existing != null && existing.isInstalled() == installed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String selection = PACK_ID + " = ?";
|
|
||||||
String[] args = new String[]{ packId };
|
|
||||||
ContentValues values = new ContentValues(1);
|
|
||||||
|
|
||||||
values.put(INSTALLED, installed ? 1 : 0);
|
|
||||||
db.update(TABLE_NAME, values, selection, args);
|
|
||||||
|
|
||||||
if (installed) {
|
|
||||||
broadcastInstallEvent(packId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileInfo saveStickerImage(@NonNull InputStream inputStream) throws IOException {
|
|
||||||
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
|
||||||
File file = File.createTempFile("sticker", ".mms", partsDirectory);
|
|
||||||
Pair<byte[], OutputStream> out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, false);
|
|
||||||
long length = Util.copy(inputStream, out.second);
|
|
||||||
|
|
||||||
return new FileInfo(file, length, out.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteSticker(@NonNull SQLiteDatabase db, long rowId, @Nullable String filePath) {
|
|
||||||
String selection = _ID + " = ?";
|
|
||||||
String[] args = new String[] { String.valueOf(rowId) };
|
|
||||||
|
|
||||||
db.delete(TABLE_NAME, selection, args);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(filePath)) {
|
|
||||||
new File(filePath).delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deletePack(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
|
||||||
String selection = PACK_ID + " = ?";
|
|
||||||
String[] args = new String[] { packId };
|
|
||||||
|
|
||||||
db.delete(TABLE_NAME, selection, args);
|
|
||||||
|
|
||||||
deleteStickersInPack(db, packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteStickersInPack(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
|
||||||
String selection = PACK_ID + " = ?";
|
|
||||||
String[] args = new String[] { packId };
|
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
|
||||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
|
|
||||||
|
|
||||||
deleteSticker(db, rowId, filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
db.delete(TABLE_NAME, selection, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteStickersInPackExceptCover(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
|
||||||
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
|
|
||||||
String[] args = new String[] { packId, "0" };
|
|
||||||
|
|
||||||
db.beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
|
|
||||||
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
|
||||||
|
|
||||||
deleteSticker(db, rowId, filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void broadcastInstallEvent(@NonNull String packId) {
|
|
||||||
StickerPackRecord pack = getStickerPack(packId);
|
|
||||||
|
|
||||||
if (pack != null) {
|
|
||||||
EventBus.getDefault().postSticky(new StickerPackInstallEvent(new DecryptableUri(pack.getCover().getUri())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class FileInfo {
|
|
||||||
private final File file;
|
|
||||||
private final long length;
|
|
||||||
private final byte[] random;
|
|
||||||
|
|
||||||
private FileInfo(@NonNull File file, long length, @NonNull byte[] random) {
|
|
||||||
this.file = file;
|
|
||||||
this.length = length;
|
|
||||||
this.random = random;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLength() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getRandom() {
|
|
||||||
return random;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class StickerRecordReader implements Closeable {
|
|
||||||
|
|
||||||
private final Cursor cursor;
|
|
||||||
|
|
||||||
public StickerRecordReader(@Nullable Cursor cursor) {
|
|
||||||
this.cursor = cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable StickerRecord getNext() {
|
|
||||||
if (cursor == null || !cursor.moveToNext()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull StickerRecord getCurrent() {
|
|
||||||
return new StickerRecord(cursor.getLong(cursor.getColumnIndexOrThrow(_ID)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
|
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(EMOJI)),
|
|
||||||
cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LENGTH)),
|
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(COVER)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class StickerPackRecordReader implements Closeable {
|
|
||||||
|
|
||||||
private final Cursor cursor;
|
|
||||||
|
|
||||||
public StickerPackRecordReader(@Nullable Cursor cursor) {
|
|
||||||
this.cursor = cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable StickerPackRecord getNext() {
|
|
||||||
if (cursor == null || !cursor.moveToNext()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull StickerPackRecord getCurrent() {
|
|
||||||
StickerRecord cover = new StickerRecordReader(cursor).getCurrent();
|
|
||||||
|
|
||||||
return new StickerPackRecord(cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_TITLE)),
|
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_AUTHOR)),
|
|
||||||
cover,
|
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(INSTALLED)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.database.PushDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||||
@ -97,8 +96,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
for (String sql : JobDatabase.CREATE_TABLE) {
|
for (String sql : JobDatabase.CREATE_TABLE) {
|
||||||
db.execSQL(sql);
|
db.execSQL(sql);
|
||||||
}
|
}
|
||||||
db.execSQL(StickerDatabase.CREATE_TABLE);
|
|
||||||
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
||||||
@ -132,7 +129,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
||||||
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
||||||
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
||||||
executeStatements(db, StickerDatabase.CREATE_INDEXES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -246,7 +242,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
if (oldVersion < lokiV21) {
|
if (oldVersion < lokiV21) {
|
||||||
deleteJobRecords(db,
|
deleteJobRecords(db,
|
||||||
"ClosedGroupUpdateMessageSendJob",
|
"ClosedGroupUpdateMessageSendJob",
|
||||||
"NullMessageSendJob");
|
"NullMessageSendJob",
|
||||||
|
"StickerDownloadJob",
|
||||||
|
"StickerPackDownloadJob");
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -6,8 +6,6 @@ import org.session.libsignal.libsignal.util.guava.Optional;
|
|||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageSender;
|
import org.session.libsignal.service.api.SignalServiceMessageSender;
|
||||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
import org.session.libsignal.service.api.util.CredentialsProvider;
|
||||||
import org.session.libsignal.service.api.util.SleepTimer;
|
|
||||||
import org.session.libsignal.service.api.util.UptimeSleepTimer;
|
|
||||||
import org.session.libsignal.service.api.websocket.ConnectivityListener;
|
import org.session.libsignal.service.api.websocket.ConnectivityListener;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||||
@ -24,16 +22,11 @@ import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
||||||
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
|
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
|
||||||
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
|
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StickerDownloadJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
|
|
||||||
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
|
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
|
||||||
@ -55,10 +48,6 @@ import network.loki.messenger.BuildConfig;
|
|||||||
TypingSendJob.class,
|
TypingSendJob.class,
|
||||||
AttachmentUploadJob.class,
|
AttachmentUploadJob.class,
|
||||||
PushDecryptJob.class,
|
PushDecryptJob.class,
|
||||||
StickerDownloadJob.class,
|
|
||||||
StickerPackPreviewRepository.class,
|
|
||||||
StickerRemoteUriLoader.Factory.class,
|
|
||||||
StickerPackDownloadJob.class,
|
|
||||||
LinkPreviewRepository.class})
|
LinkPreviewRepository.class})
|
||||||
|
|
||||||
public class SignalCommunicationModule {
|
public class SignalCommunicationModule {
|
||||||
@ -95,12 +84,7 @@ public class SignalCommunicationModule {
|
|||||||
@Provides
|
@Provides
|
||||||
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
|
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
|
||||||
if (this.messageReceiver == null) {
|
if (this.messageReceiver == null) {
|
||||||
SleepTimer sleepTimer = TextSecurePreferences.isFcmDisabled(context) ? new RealtimeSleepTimer(context) : new UptimeSleepTimer();
|
this.messageReceiver = new SignalServiceMessageReceiver();
|
||||||
|
|
||||||
this.messageReceiver = new SignalServiceMessageReceiver(new DynamicCredentialsProvider(context),
|
|
||||||
BuildConfig.USER_AGENT,
|
|
||||||
new PipeConnectivityListener(),
|
|
||||||
sleepTimer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.messageReceiver;
|
return this.messageReceiver;
|
||||||
|
@ -45,7 +45,6 @@ public final class JobManagerFactories {
|
|||||||
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
||||||
put(PushGroupUpdateJob.KEY, new PushGroupUpdateJob.Factory());
|
put(PushGroupUpdateJob.KEY, new PushGroupUpdateJob.Factory());
|
||||||
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
|
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
|
||||||
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
|
|
||||||
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
|
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
|
||||||
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
|
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
|
||||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
||||||
@ -54,8 +53,6 @@ public final class JobManagerFactories {
|
|||||||
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());
|
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());
|
||||||
put(SmsSendJob.KEY, new SmsSendJob.Factory());
|
put(SmsSendJob.KEY, new SmsSendJob.Factory());
|
||||||
put(SmsSentJob.KEY, new SmsSentJob.Factory());
|
put(SmsSentJob.KEY, new SmsSentJob.Factory());
|
||||||
put(StickerDownloadJob.KEY, new StickerDownloadJob.Factory());
|
|
||||||
put(StickerPackDownloadJob.KEY, new StickerPackDownloadJob.Factory());
|
|
||||||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||||
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.jobs.Data;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.model.IncomingSticker;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
|
||||||
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class StickerDownloadJob extends BaseJob implements InjectableType {
|
|
||||||
|
|
||||||
public static final String KEY = "StickerDownloadJob";
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerDownloadJob.class);
|
|
||||||
|
|
||||||
private static final String KEY_PACK_ID = "pack_id";
|
|
||||||
private static final String KEY_PACK_KEY = "pack_key";
|
|
||||||
private static final String KEY_PACK_TITLE = "pack_title";
|
|
||||||
private static final String KEY_PACK_AUTHOR = "pack_author";
|
|
||||||
private static final String KEY_STICKER_ID = "sticker_id";
|
|
||||||
private static final String KEY_EMOJI = "emoji";
|
|
||||||
private static final String KEY_COVER = "cover";
|
|
||||||
private static final String KEY_INSTALLED = "installed";
|
|
||||||
|
|
||||||
private final IncomingSticker sticker;
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageReceiver receiver;
|
|
||||||
|
|
||||||
public StickerDownloadJob(@NonNull IncomingSticker sticker) {
|
|
||||||
this(new Job.Parameters.Builder()
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
|
||||||
.build(),
|
|
||||||
sticker);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StickerDownloadJob(@NonNull Job.Parameters parameters, @NonNull IncomingSticker sticker) {
|
|
||||||
super(parameters);
|
|
||||||
this.sticker = sticker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRun() throws Exception {
|
|
||||||
StickerDatabase db = DatabaseFactory.getStickerDatabase(context);
|
|
||||||
|
|
||||||
if (db.getSticker(sticker.getPackId(), sticker.getStickerId(), sticker.isCover()) != null) {
|
|
||||||
Log.w(TAG, "Sticker already downloaded.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!db.isPackInstalled(sticker.getPackId()) && !sticker.isCover()) {
|
|
||||||
Log.w(TAG, "Pack is no longer installed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] packIdBytes = Hex.fromStringCondensed(sticker.getPackId());
|
|
||||||
byte[] packKeyBytes = Hex.fromStringCondensed(sticker.getPackKey());
|
|
||||||
InputStream stream = receiver.retrieveSticker(packIdBytes, packKeyBytes, sticker.getStickerId());
|
|
||||||
|
|
||||||
db.insertSticker(sticker, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
|
||||||
return e instanceof PushNetworkException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
Data serialize() {
|
|
||||||
return new Data.Builder().putString(KEY_PACK_ID, sticker.getPackId())
|
|
||||||
.putString(KEY_PACK_KEY, sticker.getPackKey())
|
|
||||||
.putString(KEY_PACK_TITLE, sticker.getPackTitle())
|
|
||||||
.putString(KEY_PACK_AUTHOR, sticker.getPackAuthor())
|
|
||||||
.putInt(KEY_STICKER_ID, sticker.getStickerId())
|
|
||||||
.putString(KEY_EMOJI, sticker.getEmoji())
|
|
||||||
.putBoolean(KEY_COVER, sticker.isCover())
|
|
||||||
.putBoolean(KEY_INSTALLED, sticker.isInstalled())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull String getFactoryKey() {
|
|
||||||
return KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCanceled() {
|
|
||||||
Log.w(TAG, "Failed to download sticker!");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<StickerDownloadJob> {
|
|
||||||
@Override
|
|
||||||
public @NonNull StickerDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
||||||
IncomingSticker sticker = new IncomingSticker(data.getString(KEY_PACK_ID),
|
|
||||||
data.getString(KEY_PACK_KEY),
|
|
||||||
data.getString(KEY_PACK_TITLE),
|
|
||||||
data.getString(KEY_PACK_AUTHOR),
|
|
||||||
data.getInt(KEY_STICKER_ID),
|
|
||||||
data.getString(KEY_EMOJI),
|
|
||||||
data.getBoolean(KEY_COVER),
|
|
||||||
data.getBoolean(KEY_INSTALLED));
|
|
||||||
|
|
||||||
return new StickerDownloadJob(parameters, sticker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.jobs.Data;
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.model.IncomingSticker;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest;
|
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest.StickerInfo;
|
|
||||||
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class StickerPackDownloadJob extends BaseJob implements InjectableType {
|
|
||||||
|
|
||||||
public static final String KEY = "StickerPackDownloadJob";
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerPackDownloadJob.class);
|
|
||||||
|
|
||||||
private static final String KEY_PACK_ID = "pack_key";
|
|
||||||
private static final String KEY_PACK_KEY = "pack_id";
|
|
||||||
private static final String KEY_REFERENCE_PACK = "reference_pack";
|
|
||||||
|
|
||||||
private final String packId;
|
|
||||||
private final String packKey;
|
|
||||||
private final boolean isReferencePack;
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageReceiver receiver;
|
|
||||||
|
|
||||||
public StickerPackDownloadJob(@NonNull String packId, @NonNull String packKey, boolean isReferencePack)
|
|
||||||
{
|
|
||||||
this(new Parameters.Builder()
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
|
||||||
.setQueue("StickerPackDownloadJob_" + packKey)
|
|
||||||
.build(),
|
|
||||||
packId,
|
|
||||||
packKey,
|
|
||||||
isReferencePack);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StickerPackDownloadJob(@NonNull Parameters parameters,
|
|
||||||
@NonNull String packId,
|
|
||||||
@NonNull String packKey,
|
|
||||||
boolean isReferencePack)
|
|
||||||
{
|
|
||||||
super(parameters);
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
this.isReferencePack = isReferencePack;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRun() throws IOException, InvalidMessageException {
|
|
||||||
if (isReferencePack && !DatabaseFactory.getAttachmentDatabase(context).containsStickerPackId(packId)) {
|
|
||||||
Log.w(TAG, "There are no attachments with the requested packId present for this reference pack. Skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isReferencePack && DatabaseFactory.getStickerDatabase(context).isPackAvailableAsReference(packId)) {
|
|
||||||
Log.i(TAG, "Sticker pack already available for reference. Skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
|
||||||
StickerDatabase stickerDatabase = DatabaseFactory.getStickerDatabase(context);
|
|
||||||
byte[] packIdBytes = Hex.fromStringCondensed(packId);
|
|
||||||
byte[] packKeyBytes = Hex.fromStringCondensed(packKey);
|
|
||||||
SignalServiceStickerManifest manifest = receiver.retrieveStickerManifest(packIdBytes, packKeyBytes);
|
|
||||||
|
|
||||||
if (manifest.getStickers().isEmpty()) {
|
|
||||||
Log.w(TAG, "No stickers in pack!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isReferencePack && stickerDatabase.isPackAvailableAsReference(packId)) {
|
|
||||||
stickerDatabase.markPackAsInstalled(packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
StickerInfo cover = manifest.getCover().or(manifest.getStickers().get(0));
|
|
||||||
JobManager.Chain chain = jobManager.startChain(new StickerDownloadJob(new IncomingSticker(packId,
|
|
||||||
packKey,
|
|
||||||
manifest.getTitle().or(""),
|
|
||||||
manifest.getAuthor().or(""),
|
|
||||||
cover.getId(),
|
|
||||||
"",
|
|
||||||
true,
|
|
||||||
!isReferencePack)));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!isReferencePack) {
|
|
||||||
List<Job> jobs = new ArrayList<>(manifest.getStickers().size());
|
|
||||||
|
|
||||||
for (StickerInfo stickerInfo : manifest.getStickers()) {
|
|
||||||
jobs.add(new StickerDownloadJob(new IncomingSticker(packId,
|
|
||||||
packKey,
|
|
||||||
manifest.getTitle().or(""),
|
|
||||||
manifest.getAuthor().or(""),
|
|
||||||
stickerInfo.getId(),
|
|
||||||
stickerInfo.getEmoji(),
|
|
||||||
false,
|
|
||||||
true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
chain.then(jobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
chain.enqueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
|
||||||
return e instanceof PushNetworkException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
Data serialize() {
|
|
||||||
return new Data.Builder().putString(KEY_PACK_ID, packId)
|
|
||||||
.putString(KEY_PACK_KEY, packKey)
|
|
||||||
.putBoolean(KEY_REFERENCE_PACK, isReferencePack)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull String getFactoryKey() {
|
|
||||||
return KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCanceled() {
|
|
||||||
Log.w(TAG, "Failed to download manifest with pack_id: " + packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<StickerPackDownloadJob> {
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
StickerPackDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
||||||
return new StickerPackDownloadJob(parameters,
|
|
||||||
data.getString(KEY_PACK_ID),
|
|
||||||
data.getString(KEY_PACK_KEY),
|
|
||||||
data.getBoolean(KEY_REFERENCE_PACK));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,30 +7,20 @@ import android.net.Uri;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
import com.google.android.gms.common.util.IOUtils;
|
import com.google.android.gms.common.util.IOUtils;
|
||||||
|
|
||||||
|
|
||||||
import org.session.libsession.utilities.MediaTypes;
|
import org.session.libsession.utilities.MediaTypes;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.net.CallRequestController;
|
import org.thoughtcrime.securesms.net.CallRequestController;
|
||||||
import org.thoughtcrime.securesms.net.CompositeRequestController;
|
import org.thoughtcrime.securesms.net.CompositeRequestController;
|
||||||
import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor;
|
import org.thoughtcrime.securesms.net.ContentProxySafetyInterceptor;
|
||||||
import org.thoughtcrime.securesms.net.RequestController;
|
import org.thoughtcrime.securesms.net.RequestController;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.libsignal.util.Pair;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest;
|
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest.StickerInfo;
|
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.OpenGraph;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.OpenGraph;
|
||||||
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||||
@ -41,7 +31,6 @@ import org.session.libsession.utilities.concurrent.SignalExecutors;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@ -82,31 +71,27 @@ public class LinkPreviewRepository implements InjectableType {
|
|||||||
|
|
||||||
RequestController metadataController;
|
RequestController metadataController;
|
||||||
|
|
||||||
if (StickerUrl.isValidShareLink(url)) {
|
metadataController = fetchMetadata(url, metadata -> {
|
||||||
metadataController = fetchStickerPackLinkPreview(context, url, callback);
|
if (metadata.isEmpty()) {
|
||||||
} else {
|
callback.onComplete(Optional.absent());
|
||||||
metadataController = fetchMetadata(url, metadata -> {
|
return;
|
||||||
if (metadata.isEmpty()) {
|
}
|
||||||
|
|
||||||
|
if (!metadata.getImageUrl().isPresent()) {
|
||||||
|
callback.onComplete(Optional.of(new LinkPreview(url, metadata.getTitle().get(), Optional.absent())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestController imageController = fetchThumbnail(context, metadata.getImageUrl().get(), attachment -> {
|
||||||
|
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
|
||||||
callback.onComplete(Optional.absent());
|
callback.onComplete(Optional.absent());
|
||||||
return;
|
} else {
|
||||||
|
callback.onComplete(Optional.of(new LinkPreview(url, metadata.getTitle().or(""), attachment)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!metadata.getImageUrl().isPresent()) {
|
|
||||||
callback.onComplete(Optional.of(new LinkPreview(url, metadata.getTitle().get(), Optional.absent())));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestController imageController = fetchThumbnail(context, metadata.getImageUrl().get(), attachment -> {
|
|
||||||
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
|
|
||||||
callback.onComplete(Optional.absent());
|
|
||||||
} else {
|
|
||||||
callback.onComplete(Optional.of(new LinkPreview(url, metadata.getTitle().or(""), attachment)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
compositeController.addController(imageController);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
compositeController.addController(imageController);
|
||||||
|
});
|
||||||
|
|
||||||
compositeController.addController(metadataController);
|
compositeController.addController(metadataController);
|
||||||
return compositeController;
|
return compositeController;
|
||||||
@ -190,66 +175,6 @@ public class LinkPreviewRepository implements InjectableType {
|
|||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestController fetchStickerPackLinkPreview(@NonNull Context context,
|
|
||||||
@NonNull String packUrl,
|
|
||||||
@NonNull Callback<Optional<LinkPreview>> callback)
|
|
||||||
{
|
|
||||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
|
||||||
try {
|
|
||||||
Pair<String, String> stickerParams = StickerUrl.parseShareLink(packUrl).or(new Pair<>("", ""));
|
|
||||||
String packIdString = stickerParams.first();
|
|
||||||
String packKeyString = stickerParams.second();
|
|
||||||
byte[] packIdBytes = Hex.fromStringCondensed(packIdString);
|
|
||||||
byte[] packKeyBytes = Hex.fromStringCondensed(packKeyString);
|
|
||||||
|
|
||||||
SignalServiceStickerManifest manifest = messageReceiver.retrieveStickerManifest(packIdBytes, packKeyBytes);
|
|
||||||
|
|
||||||
String title = manifest.getTitle().or(manifest.getAuthor()).or("");
|
|
||||||
Optional<StickerInfo> firstSticker = Optional.fromNullable(manifest.getStickers().size() > 0 ? manifest.getStickers().get(0) : null);
|
|
||||||
Optional<StickerInfo> cover = manifest.getCover().or(firstSticker);
|
|
||||||
|
|
||||||
if (cover.isPresent()) {
|
|
||||||
Bitmap bitmap = GlideApp.with(context).asBitmap()
|
|
||||||
.load(new StickerRemoteUri(packIdString, packKeyString, cover.get().getId()))
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.centerInside()
|
|
||||||
.submit(512, 512)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.WEBP, 80, baos);
|
|
||||||
|
|
||||||
byte[] bytes = baos.toByteArray();
|
|
||||||
Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory();
|
|
||||||
Optional<Attachment> thumbnail = Optional.of(new UriAttachment(uri,
|
|
||||||
uri,
|
|
||||||
MediaTypes.IMAGE_WEBP,
|
|
||||||
AttachmentDatabase.TRANSFER_PROGRESS_STARTED,
|
|
||||||
bytes.length,
|
|
||||||
bitmap.getWidth(),
|
|
||||||
bitmap.getHeight(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null));
|
|
||||||
|
|
||||||
callback.onComplete(Optional.of(new LinkPreview(packUrl, title, thumbnail)));
|
|
||||||
} else {
|
|
||||||
callback.onComplete(Optional.absent());
|
|
||||||
}
|
|
||||||
} catch (IOException | InvalidMessageException | ExecutionException | InterruptedException e) {
|
|
||||||
Log.w(TAG, "Failed to fetch sticker pack link preview.");
|
|
||||||
callback.onComplete(Optional.absent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () -> Log.i(TAG, "Cancelled sticker pack link preview fetch -- no effect.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<Attachment> bitmapToAttachment(@Nullable Bitmap bitmap,
|
private static Optional<Attachment> bitmapToAttachment(@Nullable Bitmap bitmap,
|
||||||
@NonNull Bitmap.CompressFormat format,
|
@NonNull Bitmap.CompressFormat format,
|
||||||
@NonNull String contentType)
|
@NonNull String contentType)
|
||||||
|
@ -12,7 +12,6 @@ import android.text.util.Linkify;
|
|||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
@ -62,7 +61,6 @@ public final class LinkPreviewUtil {
|
|||||||
*/
|
*/
|
||||||
public static boolean isValidLinkUrl(@Nullable String linkUrl) {
|
public static boolean isValidLinkUrl(@Nullable String linkUrl) {
|
||||||
if (linkUrl == null) return false;
|
if (linkUrl == null) return false;
|
||||||
if (StickerUrl.isValidShareLink(linkUrl)) return true;
|
|
||||||
|
|
||||||
HttpUrl url = HttpUrl.parse(linkUrl);
|
HttpUrl url = HttpUrl.parse(linkUrl);
|
||||||
return url != null &&
|
return url != null &&
|
||||||
|
@ -52,7 +52,6 @@ public class PartAuthority {
|
|||||||
switch (match) {
|
switch (match) {
|
||||||
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
||||||
case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId());
|
case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId());
|
||||||
case STICKER_ROW: return DatabaseFactory.getStickerDatabase(context).getStickerStream(ContentUris.parseId(uri));
|
|
||||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||||
default: return context.getContentResolver().openInputStream(uri);
|
default: return context.getContentResolver().openInputStream(uri);
|
||||||
|
@ -34,8 +34,6 @@ import org.thoughtcrime.securesms.glide.cache.EncryptedGifCacheDecoder;
|
|||||||
import org.thoughtcrime.securesms.glide.cache.EncryptedGifDrawableResourceEncoder;
|
import org.thoughtcrime.securesms.glide.cache.EncryptedGifDrawableResourceEncoder;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -71,7 +69,6 @@ public class SignalGlideModule extends AppGlideModule {
|
|||||||
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
|
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
|
||||||
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
||||||
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
||||||
registry.append(StickerRemoteUri.class, InputStream.class, new StickerRemoteUriLoader.Factory(context));
|
|
||||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,13 +4,9 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
|
||||||
|
|
||||||
public class BootReceiver extends BroadcastReceiver {
|
public class BootReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushNotificationReceiveJob(context));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maintains a list of "blessed" sticker packs that essentially serve as defaults.
|
|
||||||
*/
|
|
||||||
public final class BlessedPacks {
|
|
||||||
|
|
||||||
private static final Set<String> BLESSED_PACK_IDS = new HashSet<>();
|
|
||||||
|
|
||||||
public static boolean contains(@NonNull String packId) {
|
|
||||||
return BLESSED_PACK_IDS.contains(packId);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.Px;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter for a specific page in the sticker keyboard. Shows the stickers in a grid.
|
|
||||||
* @see StickerKeyboardPageFragment
|
|
||||||
*/
|
|
||||||
final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeyboardPageAdapter.StickerKeyboardPageViewHolder> {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
private final EventListener eventListener;
|
|
||||||
private final List<StickerRecord> stickers;
|
|
||||||
|
|
||||||
private int stickerSize;
|
|
||||||
|
|
||||||
StickerKeyboardPageAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.stickers = new ArrayList<>();
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return stickers.get(position).getRowId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
StickerKeyboardPageViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
|
||||||
return new StickerKeyboardPageViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_keyboard_page_list_item, viewGroup, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull StickerKeyboardPageViewHolder viewHolder, int i) {
|
|
||||||
viewHolder.bind(glideRequests, eventListener, stickers.get(i), stickerSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewRecycled(@NonNull StickerKeyboardPageViewHolder holder) {
|
|
||||||
holder.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return stickers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStickers(@NonNull List<StickerRecord> stickers, @Px int stickerSize) {
|
|
||||||
this.stickers.clear();
|
|
||||||
this.stickers.addAll(stickers);
|
|
||||||
|
|
||||||
this.stickerSize = stickerSize;
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStickerSize(@Px int stickerSize) {
|
|
||||||
this.stickerSize = stickerSize;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
static class StickerKeyboardPageViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ImageView image;
|
|
||||||
|
|
||||||
private StickerRecord currentSticker;
|
|
||||||
|
|
||||||
public StickerKeyboardPageViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
image = itemView.findViewById(R.id.sticker_keyboard_page_image);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(@NonNull GlideRequests glideRequests,
|
|
||||||
@Nullable EventListener eventListener,
|
|
||||||
@NonNull StickerRecord sticker,
|
|
||||||
@Px int size)
|
|
||||||
{
|
|
||||||
currentSticker = sticker;
|
|
||||||
|
|
||||||
itemView.getLayoutParams().height = size;
|
|
||||||
itemView.getLayoutParams().width = size;
|
|
||||||
itemView.requestLayout();
|
|
||||||
|
|
||||||
glideRequests.load(new DecryptableUri(sticker.getUri()))
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(image);
|
|
||||||
|
|
||||||
if (eventListener != null) {
|
|
||||||
image.setOnClickListener(v -> eventListener.onStickerClicked(sticker));
|
|
||||||
image.setOnLongClickListener(v -> {
|
|
||||||
eventListener.onStickerLongClicked(v);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
image.setOnClickListener(null);
|
|
||||||
image.setOnLongClickListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void recycle() {
|
|
||||||
image.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable StickerRecord getCurrentSticker() {
|
|
||||||
return currentSticker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventListener {
|
|
||||||
void onStickerClicked(@NonNull StickerRecord sticker);
|
|
||||||
void onStickerLongClicked(@NonNull View targetView);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.Px;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardPageAdapter.StickerKeyboardPageViewHolder;
|
|
||||||
import org.session.libsession.utilities.ViewUtil;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An individual page of stickers in the {@link StickerKeyboardProvider}.
|
|
||||||
*/
|
|
||||||
public final class StickerKeyboardPageFragment extends Fragment implements StickerKeyboardPageAdapter.EventListener {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerKeyboardPageFragment.class);
|
|
||||||
|
|
||||||
private static final String KEY_PACK_ID = "pack_id";
|
|
||||||
|
|
||||||
public static final String RECENT_PACK_ID = StickerKeyboardPageViewModel.RECENT_PACK_ID;
|
|
||||||
|
|
||||||
private RecyclerView list;
|
|
||||||
private StickerKeyboardPageAdapter adapter;
|
|
||||||
private GridLayoutManager layoutManager;
|
|
||||||
|
|
||||||
private StickerKeyboardPageViewModel viewModel;
|
|
||||||
private EventListener eventListener;
|
|
||||||
private ListTouchListener listTouchListener;
|
|
||||||
|
|
||||||
private String packId;
|
|
||||||
|
|
||||||
public static StickerKeyboardPageFragment newInstance(@NonNull String packId) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString(KEY_PACK_ID, packId);
|
|
||||||
|
|
||||||
StickerKeyboardPageFragment fragment = new StickerKeyboardPageFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
fragment.packId = packId;
|
|
||||||
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
return inflater.inflate(R.layout.sticker_keyboard_page, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
GlideRequests glideRequests = GlideApp.with(this);
|
|
||||||
|
|
||||||
this.list = view.findViewById(R.id.sticker_keyboard_list);
|
|
||||||
this.adapter = new StickerKeyboardPageAdapter(glideRequests, this);
|
|
||||||
this.layoutManager = new GridLayoutManager(requireContext(), 2);
|
|
||||||
this.listTouchListener = new ListTouchListener(requireContext(), glideRequests);
|
|
||||||
this.packId = getArguments().getString(KEY_PACK_ID);
|
|
||||||
|
|
||||||
list.setLayoutManager(layoutManager);
|
|
||||||
list.setAdapter(adapter);
|
|
||||||
list.addOnItemTouchListener(listTouchListener);
|
|
||||||
|
|
||||||
initViewModel(packId);
|
|
||||||
onScreenWidthChanged(getScreenWidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
onScreenWidthChanged(getScreenWidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerClicked(@NonNull StickerRecord sticker) {
|
|
||||||
if (eventListener != null) {
|
|
||||||
eventListener.onStickerSelected(sticker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerLongClicked(@NonNull View targetView) {
|
|
||||||
if (listTouchListener != null) {
|
|
||||||
listTouchListener.enterHoverMode(list, targetView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEventListener(@NonNull EventListener eventListener) {
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackId() {
|
|
||||||
return packId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViewModel(@NonNull String packId) {
|
|
||||||
StickerKeyboardRepository repository = new StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(requireContext()));
|
|
||||||
viewModel = ViewModelProviders.of(this, new StickerKeyboardPageViewModel.Factory(requireActivity().getApplication(), repository)).get(StickerKeyboardPageViewModel.class);
|
|
||||||
|
|
||||||
viewModel.getStickers(packId).observe(getViewLifecycleOwner(), stickerRecords -> {
|
|
||||||
if (stickerRecords == null) return;
|
|
||||||
|
|
||||||
adapter.setStickers(stickerRecords, calculateStickerSize(getScreenWidth()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onScreenWidthChanged(@Px int newWidth) {
|
|
||||||
if (layoutManager != null) {
|
|
||||||
layoutManager.setSpanCount(calculateColumnCount(newWidth));
|
|
||||||
adapter.setStickerSize(calculateStickerSize(newWidth));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getScreenWidth() {
|
|
||||||
Point size = new Point();
|
|
||||||
requireActivity().getWindowManager().getDefaultDisplay().getSize(size);
|
|
||||||
return size.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calculateColumnCount(@Px int screenWidth) {
|
|
||||||
float modifier = getResources().getDimensionPixelOffset(R.dimen.sticker_page_item_padding);
|
|
||||||
float divisor = getResources().getDimensionPixelOffset(R.dimen.sticker_page_item_divisor);
|
|
||||||
return (int) ((screenWidth - modifier) / divisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calculateStickerSize(@Px int screenWidth) {
|
|
||||||
float multiplier = getResources().getDimensionPixelOffset(R.dimen.sticker_page_item_multiplier);
|
|
||||||
int columnCount = calculateColumnCount(screenWidth);
|
|
||||||
|
|
||||||
return (int) ((screenWidth - ((columnCount + 1) * multiplier)) / columnCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ListTouchListener implements RecyclerView.OnItemTouchListener {
|
|
||||||
|
|
||||||
private final StickerPreviewPopup popup;
|
|
||||||
|
|
||||||
private boolean hoverMode;
|
|
||||||
|
|
||||||
ListTouchListener(@NonNull Context context, @NonNull GlideRequests glideRequests) {
|
|
||||||
this.popup = new StickerPreviewPopup(context, glideRequests);
|
|
||||||
popup.setAnimationStyle(R.style.StickerPopupAnimation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
|
|
||||||
return hoverMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
|
|
||||||
switch (motionEvent.getAction()) {
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
|
||||||
hoverMode = false;
|
|
||||||
popup.dismiss();
|
|
||||||
eventListener.onStickerPopupEnded();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
for (int i = 0, len = recyclerView.getChildCount(); i < len; i++) {
|
|
||||||
View child = recyclerView.getChildAt(i);
|
|
||||||
|
|
||||||
if (ViewUtil.isPointInsideView(recyclerView, motionEvent.getRawX(), motionEvent.getRawY()) &&
|
|
||||||
ViewUtil.isPointInsideView(child, motionEvent.getRawX(), motionEvent.getRawY()))
|
|
||||||
{
|
|
||||||
showStickerForView(recyclerView, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestDisallowInterceptTouchEvent(boolean b) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void enterHoverMode(@NonNull RecyclerView recyclerView, View targetView) {
|
|
||||||
this.hoverMode = true;
|
|
||||||
showStickerForView(recyclerView, targetView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showStickerForView(@NonNull RecyclerView recyclerView, @NonNull View view) {
|
|
||||||
StickerKeyboardPageViewHolder holder = (StickerKeyboardPageViewHolder) recyclerView.getChildViewHolder(view);
|
|
||||||
|
|
||||||
if (holder != null && holder.getCurrentSticker() != null) {
|
|
||||||
if (!popup.isShowing()) {
|
|
||||||
popup.showAtLocation(recyclerView, Gravity.NO_GRAVITY, 0, 0);
|
|
||||||
eventListener.onStickerPopupStarted();
|
|
||||||
}
|
|
||||||
popup.presentSticker(holder.getCurrentSticker());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventListener {
|
|
||||||
void onStickerSelected(@NonNull StickerRecord sticker);
|
|
||||||
void onStickerPopupStarted();
|
|
||||||
void onStickerPopupEnded();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.session.libsession.utilities.Throttler;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
final class StickerKeyboardPageViewModel extends ViewModel {
|
|
||||||
|
|
||||||
static final String RECENT_PACK_ID = "RECENT";
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final StickerKeyboardRepository repository;
|
|
||||||
private final MutableLiveData<List<StickerRecord>> stickers;
|
|
||||||
private final Throttler observerThrottler;
|
|
||||||
private final ContentObserver observer;
|
|
||||||
|
|
||||||
private String packId;
|
|
||||||
|
|
||||||
private StickerKeyboardPageViewModel(@NonNull Application application, @NonNull StickerKeyboardRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
this.stickers = new MutableLiveData<>();
|
|
||||||
this.observerThrottler = new Throttler(500);
|
|
||||||
this.observer = new ContentObserver(new Handler()) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
observerThrottler.publish(() -> getStickers(packId));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.Sticker.CONTENT_URI, true, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<List<StickerRecord>> getStickers(@NonNull String packId) {
|
|
||||||
this.packId = packId;
|
|
||||||
|
|
||||||
if (RECENT_PACK_ID.equals(packId)) {
|
|
||||||
repository.getRecentStickers(stickers::postValue);
|
|
||||||
} else {
|
|
||||||
repository.getStickersForPack(packId, stickers::postValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
application.getContentResolver().unregisterContentObserver(observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
|
||||||
private final Application application;
|
|
||||||
private final StickerKeyboardRepository repository;
|
|
||||||
|
|
||||||
Factory(@NonNull Application application, @NonNull StickerKeyboardRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return modelClass.cast(new StickerKeyboardPageViewModel(application, repository));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,259 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardPageFragment.EventListener;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardRepository.PackListResult;
|
|
||||||
import org.thoughtcrime.securesms.util.ResUtil;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.Throttler;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A provider to select stickers in the {@link org.thoughtcrime.securesms.components.emoji.MediaKeyboard}.
|
|
||||||
*/
|
|
||||||
public final class StickerKeyboardProvider implements MediaKeyboardProvider,
|
|
||||||
MediaKeyboardProvider.AddObserver,
|
|
||||||
StickerKeyboardPageFragment.EventListener
|
|
||||||
{
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final StickerEventListener eventListener;
|
|
||||||
private final StickerPagerAdapter pagerAdapter;
|
|
||||||
private final Throttler stickerThrottler;
|
|
||||||
|
|
||||||
private Controller controller;
|
|
||||||
private Presenter presenter;
|
|
||||||
private boolean isSoloProvider;
|
|
||||||
private StickerKeyboardViewModel viewModel;
|
|
||||||
|
|
||||||
public StickerKeyboardProvider(@NonNull AppCompatActivity activity,
|
|
||||||
@NonNull StickerEventListener eventListener)
|
|
||||||
{
|
|
||||||
this.context = activity;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.pagerAdapter = new StickerPagerAdapter(activity.getSupportFragmentManager(), this);
|
|
||||||
this.stickerThrottler = new Throttler(100);
|
|
||||||
|
|
||||||
initViewModel(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getProviderIconView(boolean selected) {
|
|
||||||
if (selected) {
|
|
||||||
return ThemeUtil.isDarkTheme(context) ? R.layout.sticker_keyboard_icon_dark_selected : R.layout.sticker_keyboard_icon_light_selected;
|
|
||||||
} else {
|
|
||||||
return ThemeUtil.isDarkTheme(context) ? R.layout.sticker_keyboard_icon_dark : R.layout.sticker_keyboard_icon_light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider) {
|
|
||||||
this.presenter = presenter;
|
|
||||||
this.isSoloProvider = isSoloProvider;
|
|
||||||
|
|
||||||
PackListResult result = viewModel.getPacks().getValue();
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
present(presenter, result, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setController(@Nullable Controller controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAddClicked() {
|
|
||||||
eventListener.onStickerManagementClicked();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerSelected(@NonNull StickerRecord sticker) {
|
|
||||||
stickerThrottler.publish(() -> eventListener.onStickerSelected(sticker));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPopupStarted() {
|
|
||||||
if (controller != null) {
|
|
||||||
controller.setViewPagerEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPopupEnded() {
|
|
||||||
if (controller != null) {
|
|
||||||
controller.setViewPagerEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViewModel(@NonNull AppCompatActivity activity) {
|
|
||||||
StickerKeyboardRepository repository = new StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(activity));
|
|
||||||
viewModel = ViewModelProviders.of(activity, new StickerKeyboardViewModel.Factory(activity.getApplication(), repository)).get(StickerKeyboardViewModel.class);
|
|
||||||
|
|
||||||
viewModel.getPacks().observe(activity, result -> {
|
|
||||||
if (result == null) return;
|
|
||||||
|
|
||||||
int previousCount = pagerAdapter.getCount();
|
|
||||||
|
|
||||||
pagerAdapter.setPacks(result.getPacks());
|
|
||||||
|
|
||||||
if (presenter != null) {
|
|
||||||
present(presenter, result, previousCount != pagerAdapter.getCount());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void present(@NonNull Presenter presenter, @NonNull PackListResult result, boolean calculateStartingIndex) {
|
|
||||||
if (result.getPacks().isEmpty() && presenter.isVisible()) {
|
|
||||||
context.startActivity(StickerManagementActivity.getIntent(context));
|
|
||||||
presenter.requestDismissal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startingIndex = presenter.getCurrentPosition();
|
|
||||||
|
|
||||||
if (calculateStartingIndex) {
|
|
||||||
startingIndex = !result.hasRecents() && result.getPacks().size() > 0 ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
presenter.present(this, pagerAdapter, new IconProvider(context, result.getPacks()), null, this, null, startingIndex);
|
|
||||||
|
|
||||||
if (isSoloProvider && result.getPacks().isEmpty()) {
|
|
||||||
context.startActivity(StickerManagementActivity.getIntent(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
return obj instanceof StickerKeyboardProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StickerPagerAdapter extends FragmentStatePagerAdapter {
|
|
||||||
|
|
||||||
private final List<StickerPackRecord> packs;
|
|
||||||
private final Map<String, Integer> itemPositions;
|
|
||||||
private final EventListener eventListener;
|
|
||||||
|
|
||||||
public StickerPagerAdapter(@NonNull FragmentManager fm, @NonNull EventListener eventListener) {
|
|
||||||
super(fm);
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.packs = new ArrayList<>();
|
|
||||||
this.itemPositions = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemPosition(@NonNull Object object) {
|
|
||||||
String packId = ((StickerKeyboardPageFragment) object).getPackId();
|
|
||||||
|
|
||||||
if (itemPositions.containsKey(packId)) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return itemPositions.get(packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return POSITION_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int i) {
|
|
||||||
StickerKeyboardPageFragment fragment;
|
|
||||||
|
|
||||||
if (i == 0) {
|
|
||||||
fragment = StickerKeyboardPageFragment.newInstance(StickerKeyboardPageFragment.RECENT_PACK_ID);
|
|
||||||
} else {
|
|
||||||
StickerPackRecord pack = packs.get(i - 1);
|
|
||||||
fragment = StickerKeyboardPageFragment.newInstance(pack.getPackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.setEventListener(eventListener);
|
|
||||||
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return packs.isEmpty() ? 0 : packs.size() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPacks(@NonNull List<StickerPackRecord> packs) {
|
|
||||||
itemPositions.clear();
|
|
||||||
|
|
||||||
if (areListsEqual(this.packs, packs)) {
|
|
||||||
itemPositions.put(StickerKeyboardPageFragment.RECENT_PACK_ID, 0);
|
|
||||||
for (int i = 0; i < packs.size(); i++) {
|
|
||||||
itemPositions.put(packs.get(i).getPackId(), i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.packs.clear();
|
|
||||||
this.packs.addAll(packs);
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean areListsEqual(@NonNull List<StickerPackRecord> a, @NonNull List<StickerPackRecord> b) {
|
|
||||||
if (a.size() != b.size()) return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < a.size(); i++) {
|
|
||||||
if (!a.get(i).equals(b.get(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class IconProvider implements TabIconProvider {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final List<StickerPackRecord> packs;
|
|
||||||
|
|
||||||
private IconProvider(@NonNull Context context, List<StickerPackRecord> packs) {
|
|
||||||
this.context = context;
|
|
||||||
this.packs = packs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index) {
|
|
||||||
if (index == 0) {
|
|
||||||
Drawable icon = ResUtil.getDrawable(context, R.attr.emoji_category_recent);
|
|
||||||
imageView.setImageDrawable(icon);
|
|
||||||
} else {
|
|
||||||
Uri uri = packs.get(index - 1).getCover().getUri();
|
|
||||||
|
|
||||||
glideRequests.load(new DecryptableStreamUriLoader.DecryptableUri(uri))
|
|
||||||
.into(imageView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface StickerEventListener {
|
|
||||||
void onStickerSelected(@NonNull StickerRecord sticker);
|
|
||||||
void onStickerManagementClicked();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase.StickerPackRecordReader;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase.StickerRecordReader;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.session.libsession.utilities.concurrent.SignalExecutors;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
final class StickerKeyboardRepository {
|
|
||||||
|
|
||||||
private static final int RECENT_LIMIT = 24;
|
|
||||||
|
|
||||||
private final StickerDatabase stickerDatabase;
|
|
||||||
|
|
||||||
StickerKeyboardRepository(@NonNull StickerDatabase stickerDatabase) {
|
|
||||||
this.stickerDatabase = stickerDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getPackList(@NonNull Callback<PackListResult> callback) {
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
|
||||||
List<StickerPackRecord> packs = new ArrayList<>();
|
|
||||||
|
|
||||||
try (StickerPackRecordReader reader = new StickerPackRecordReader(stickerDatabase.getInstalledStickerPacks())) {
|
|
||||||
StickerPackRecord pack;
|
|
||||||
while ((pack = reader.getNext()) != null) {
|
|
||||||
packs.add(pack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasRecents;
|
|
||||||
|
|
||||||
try (Cursor recentsCursor = stickerDatabase.getRecentlyUsedStickers(1)) {
|
|
||||||
hasRecents = recentsCursor != null && recentsCursor.moveToFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onComplete(new PackListResult(packs, hasRecents));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void getStickersForPack(@NonNull String packId, @NonNull Callback<List<StickerRecord>> callback) {
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
|
||||||
List<StickerRecord> stickers = new ArrayList<>();
|
|
||||||
|
|
||||||
try (StickerRecordReader reader = new StickerRecordReader(stickerDatabase.getStickersForPack(packId))) {
|
|
||||||
StickerRecord sticker;
|
|
||||||
while ((sticker = reader.getNext()) != null) {
|
|
||||||
stickers.add(sticker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onComplete(stickers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void getRecentStickers(@NonNull Callback<List<StickerRecord>> callback) {
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
|
||||||
List<StickerRecord> stickers = new ArrayList<>();
|
|
||||||
|
|
||||||
try (StickerRecordReader reader = new StickerRecordReader(stickerDatabase.getRecentlyUsedStickers(RECENT_LIMIT))) {
|
|
||||||
StickerRecord sticker;
|
|
||||||
while ((sticker = reader.getNext()) != null) {
|
|
||||||
stickers.add(sticker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onComplete(stickers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static class PackListResult {
|
|
||||||
|
|
||||||
private final List<StickerPackRecord> packs;
|
|
||||||
private final boolean hasRecents;
|
|
||||||
|
|
||||||
PackListResult(List<StickerPackRecord> packs, boolean hasRecents) {
|
|
||||||
this.packs = packs;
|
|
||||||
this.hasRecents = hasRecents;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<StickerPackRecord> getPacks() {
|
|
||||||
return packs;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasRecents() {
|
|
||||||
return hasRecents;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback<T> {
|
|
||||||
void onComplete(T result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerKeyboardRepository.PackListResult;
|
|
||||||
import org.session.libsession.utilities.Throttler;
|
|
||||||
|
|
||||||
final class StickerKeyboardViewModel extends ViewModel {
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final MutableLiveData<PackListResult> packs;
|
|
||||||
private final Throttler observerThrottler;
|
|
||||||
private final ContentObserver observer;
|
|
||||||
|
|
||||||
private StickerKeyboardViewModel(@NonNull Application application, @NonNull StickerKeyboardRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.packs = new MutableLiveData<>();
|
|
||||||
this.observerThrottler = new Throttler(500);
|
|
||||||
this.observer = new ContentObserver(new Handler()) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
observerThrottler.publish(() -> repository.getPackList(packs::postValue));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
repository.getPackList(packs::postValue);
|
|
||||||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull LiveData<PackListResult> getPacks() {
|
|
||||||
return packs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
application.getContentResolver().unregisterContentObserver(observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory extends ViewModelProvider.NewInstanceFactory {
|
|
||||||
private final Application application;
|
|
||||||
private final StickerKeyboardRepository repository;
|
|
||||||
|
|
||||||
public Factory(@NonNull Application application, @NonNull StickerKeyboardRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return modelClass.cast(new StickerKeyboardViewModel(application, repository));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
|
||||||
import org.thoughtcrime.securesms.ShareActivity;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows the user to view and manage (install, uninstall, etc) their stickers.
|
|
||||||
*/
|
|
||||||
public final class StickerManagementActivity extends PassphraseRequiredActionBarActivity implements StickerManagementAdapter.EventListener {
|
|
||||||
|
|
||||||
private RecyclerView list;
|
|
||||||
private StickerManagementAdapter adapter;
|
|
||||||
|
|
||||||
private StickerManagementViewModel viewModel;
|
|
||||||
|
|
||||||
public static Intent getIntent(@NonNull Context context) {
|
|
||||||
return new Intent(context, StickerManagementActivity.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
super.onPreCreate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
|
||||||
setContentView(R.layout.sticker_management_activity);
|
|
||||||
|
|
||||||
initView();
|
|
||||||
initToolbar();
|
|
||||||
initViewModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
viewModel.onVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPackClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
startActivity(StickerPackPreviewActivity.getIntent(packId, packKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPackUninstallClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
viewModel.onStickerPackUninstallClicked(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPackInstallClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
viewModel.onStickerPackInstallClicked(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStickerPackShareClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
Intent composeIntent = new Intent(this, ShareActivity.class);
|
|
||||||
composeIntent.putExtra(Intent.EXTRA_TEXT, StickerUrl.createShareLink(packId, packKey));
|
|
||||||
startActivity(composeIntent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initView() {
|
|
||||||
this.list = findViewById(R.id.sticker_management_list);
|
|
||||||
this.adapter = new StickerManagementAdapter(GlideApp.with(this), this);
|
|
||||||
|
|
||||||
list.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
list.setAdapter(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initToolbar() {
|
|
||||||
getSupportActionBar().setTitle(R.string.StickerManagementActivity_stickers);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViewModel() {
|
|
||||||
StickerManagementRepository repository = new StickerManagementRepository(this);
|
|
||||||
viewModel = ViewModelProviders.of(this, new StickerManagementViewModel.Factory(getApplication(), repository)).get(StickerManagementViewModel.class);
|
|
||||||
|
|
||||||
viewModel.init();
|
|
||||||
viewModel.getStickerPacks().observe(this, packResult -> {
|
|
||||||
if (packResult == null) return;
|
|
||||||
|
|
||||||
adapter.setPackLists(packResult.getInstalledPacks(), packResult.getAvailablePacks(), packResult.getBlessedPacks());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,330 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.util.StableIdGenerator;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
final class StickerManagementAdapter extends RecyclerView.Adapter {
|
|
||||||
|
|
||||||
private static final int TYPE_HEADER = 1;
|
|
||||||
private static final int TYPE_EMPTY = 2;
|
|
||||||
private static final int TYPE_PACK = 3;
|
|
||||||
|
|
||||||
private static final String TAG_YOUR_STICKERS = "YourStickers";
|
|
||||||
private static final String TAG_MESSAGE_STICKERS = "MessageStickers";
|
|
||||||
private static final String TAG_BLESSED_STICKERS = "BlessedStickers";
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
private final EventListener eventListener;
|
|
||||||
private final StableIdGenerator<String> stableIdGenerator;
|
|
||||||
|
|
||||||
private final List<Section> sections = new ArrayList<Section>(3) {{
|
|
||||||
Section yourStickers = new Section(TAG_YOUR_STICKERS,
|
|
||||||
R.string.StickerManagementAdapter_installed_stickers,
|
|
||||||
R.string.StickerManagementAdapter_no_stickers_installed,
|
|
||||||
new ArrayList<>(),
|
|
||||||
0);
|
|
||||||
Section messageStickers = new Section(TAG_MESSAGE_STICKERS,
|
|
||||||
R.string.StickerManagementAdapter_stickers_you_received,
|
|
||||||
R.string.StickerManagementAdapter_stickers_from_incoming_messages_will_appear_here,
|
|
||||||
new ArrayList<>(),
|
|
||||||
yourStickers.size());
|
|
||||||
|
|
||||||
add(yourStickers);
|
|
||||||
add(messageStickers);
|
|
||||||
}};
|
|
||||||
|
|
||||||
StickerManagementAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.stableIdGenerator = new StableIdGenerator<>();
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
for (Section section : sections) {
|
|
||||||
if (section.handles(position)) {
|
|
||||||
return section.getItemId(stableIdGenerator, position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NoSectionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
for (Section section : sections) {
|
|
||||||
if (section.handles(position)) {
|
|
||||||
return section.getViewType(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NoSectionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
|
|
||||||
switch (viewType) {
|
|
||||||
case TYPE_HEADER:
|
|
||||||
return new HeaderViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_management_header_item, viewGroup, false));
|
|
||||||
case TYPE_EMPTY:
|
|
||||||
return new EmptyViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_management_empty_item, viewGroup, false));
|
|
||||||
case TYPE_PACK:
|
|
||||||
return new StickerViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_management_sticker_item, viewGroup, false));
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Unexpected viewType! " + viewType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
|
||||||
for (Section section : sections) {
|
|
||||||
if (section.handles(position)) {
|
|
||||||
section.bindViewHolder(viewHolder, position, glideRequests, eventListener);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NoSectionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
|
|
||||||
if (holder instanceof StickerViewHolder) {
|
|
||||||
((StickerViewHolder) holder).recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return Stream.of(sections).reduce(0, (sum, section) -> sum + section.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPackLists(@NonNull List<StickerPackRecord> installedPacks,
|
|
||||||
@NonNull List<StickerPackRecord> availablePacks,
|
|
||||||
@NonNull List<StickerPackRecord> blessedPacks)
|
|
||||||
{
|
|
||||||
Section yourStickers = new Section(TAG_YOUR_STICKERS,
|
|
||||||
R.string.StickerManagementAdapter_installed_stickers,
|
|
||||||
R.string.StickerManagementAdapter_no_stickers_installed,
|
|
||||||
installedPacks,
|
|
||||||
0);
|
|
||||||
Section blessedStickers = new Section(TAG_BLESSED_STICKERS,
|
|
||||||
R.string.StickerManagementAdapter_signal_artist_series,
|
|
||||||
0,
|
|
||||||
blessedPacks,
|
|
||||||
yourStickers.size());
|
|
||||||
Section messageStickers = new Section(TAG_MESSAGE_STICKERS,
|
|
||||||
R.string.StickerManagementAdapter_stickers_you_received,
|
|
||||||
R.string.StickerManagementAdapter_stickers_from_incoming_messages_will_appear_here,
|
|
||||||
availablePacks,
|
|
||||||
yourStickers.size() + (blessedPacks.isEmpty() ? 0 : blessedStickers.size()));
|
|
||||||
|
|
||||||
sections.clear();
|
|
||||||
sections.add(yourStickers);
|
|
||||||
|
|
||||||
if (!blessedPacks.isEmpty()) {
|
|
||||||
sections.add(blessedStickers);
|
|
||||||
}
|
|
||||||
|
|
||||||
sections.add(messageStickers);
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Section {
|
|
||||||
private static final String STABLE_ID_HEADER = "header";
|
|
||||||
private static final String STABLE_ID_TEXT = "text";
|
|
||||||
|
|
||||||
private final String tag;
|
|
||||||
private final int titleResId;
|
|
||||||
private final int emptyResId;
|
|
||||||
private final List<StickerPackRecord> records;
|
|
||||||
private final int offset;
|
|
||||||
|
|
||||||
Section(@NonNull String tag,
|
|
||||||
@StringRes int titleResId,
|
|
||||||
@StringRes int emptyResId,
|
|
||||||
@NonNull List<StickerPackRecord> records,
|
|
||||||
int offset)
|
|
||||||
{
|
|
||||||
this.tag = tag;
|
|
||||||
this.titleResId = titleResId;
|
|
||||||
this.emptyResId = emptyResId;
|
|
||||||
this.records = records;
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getViewType(int globalPosition) {
|
|
||||||
int localPosition = globalPosition - offset;
|
|
||||||
|
|
||||||
if (localPosition == 0) {
|
|
||||||
return TYPE_HEADER;
|
|
||||||
} else if (records.isEmpty()) {
|
|
||||||
return TYPE_EMPTY;
|
|
||||||
} else {
|
|
||||||
return TYPE_PACK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long getItemId(@NonNull StableIdGenerator<String> idGenerator, int globalPosition) {
|
|
||||||
int localPosition = globalPosition - offset;
|
|
||||||
|
|
||||||
if (localPosition == 0) {
|
|
||||||
return idGenerator.getId(tag + "_" + STABLE_ID_HEADER);
|
|
||||||
} else if (records.isEmpty()) {
|
|
||||||
return idGenerator.getId(tag + "_" + STABLE_ID_TEXT);
|
|
||||||
} else {
|
|
||||||
return idGenerator.getId(records.get(localPosition - 1).getPackId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
|
|
||||||
int globalPosition,
|
|
||||||
@NonNull GlideRequests glideRequests,
|
|
||||||
@NonNull EventListener eventListener)
|
|
||||||
{
|
|
||||||
int localPosition = globalPosition - offset;
|
|
||||||
|
|
||||||
if (localPosition == 0) {
|
|
||||||
((HeaderViewHolder) viewHolder).bind(titleResId);
|
|
||||||
} else if (records.isEmpty()) {
|
|
||||||
((EmptyViewHolder) viewHolder).bind(emptyResId);
|
|
||||||
} else {
|
|
||||||
((StickerViewHolder) viewHolder).bind(glideRequests, eventListener, records.get(localPosition - 1), localPosition == records.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean handles(int globalPosition) {
|
|
||||||
int localPosition = globalPosition - offset;
|
|
||||||
return localPosition >= 0 && localPosition < size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int size() {
|
|
||||||
return records.isEmpty() ? 2 : records.size() + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class StickerViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ImageView cover;
|
|
||||||
private final TextView title;
|
|
||||||
private final TextView author;
|
|
||||||
private final View badge;
|
|
||||||
private final View divider;
|
|
||||||
private final View actionButton;
|
|
||||||
private final ImageView actionButtonImage;
|
|
||||||
private final View shareButton;
|
|
||||||
private final ImageView shareButtonImage;
|
|
||||||
|
|
||||||
StickerViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
|
|
||||||
this.cover = itemView.findViewById(R.id.sticker_management_cover);
|
|
||||||
this.title = itemView.findViewById(R.id.sticker_management_title);
|
|
||||||
this.author = itemView.findViewById(R.id.sticker_management_author);
|
|
||||||
this.badge = itemView.findViewById(R.id.sticker_management_blessed_badge);
|
|
||||||
this.divider = itemView.findViewById(R.id.sticker_management_divider);
|
|
||||||
this.actionButton = itemView.findViewById(R.id.sticker_management_action_button);
|
|
||||||
this.actionButtonImage = itemView.findViewById(R.id.sticker_management_action_button_image);
|
|
||||||
this.shareButton = itemView.findViewById(R.id.sticker_management_share_button);
|
|
||||||
this.shareButtonImage = itemView.findViewById(R.id.sticker_management_share_button_image);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(@NonNull GlideRequests glideRequests,
|
|
||||||
@NonNull EventListener eventListener,
|
|
||||||
@NonNull StickerPackRecord stickerPack,
|
|
||||||
boolean lastInList)
|
|
||||||
{
|
|
||||||
title.setText(stickerPack.getTitle().or(itemView.getResources().getString(R.string.StickerManagementAdapter_untitled)));
|
|
||||||
author.setText(stickerPack.getAuthor().or(itemView.getResources().getString(R.string.StickerManagementAdapter_unknown)));
|
|
||||||
divider.setVisibility(lastInList ? View.GONE : View.VISIBLE);
|
|
||||||
badge.setVisibility(BlessedPacks.contains(stickerPack.getPackId()) ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
glideRequests.load(new DecryptableUri(stickerPack.getCover().getUri()))
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(cover);
|
|
||||||
|
|
||||||
if (stickerPack.isInstalled()) {
|
|
||||||
actionButtonImage.setImageResource(R.drawable.ic_x);
|
|
||||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackUninstallClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
|
||||||
|
|
||||||
shareButton.setVisibility(View.VISIBLE);
|
|
||||||
shareButtonImage.setVisibility(View.VISIBLE);
|
|
||||||
shareButton.setOnClickListener(v -> eventListener.onStickerPackShareClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
|
||||||
} else {
|
|
||||||
actionButtonImage.setImageResource(R.drawable.ic_arrow_down);
|
|
||||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackInstallClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
|
||||||
|
|
||||||
shareButton.setVisibility(View.GONE);
|
|
||||||
shareButtonImage.setVisibility(View.GONE);
|
|
||||||
shareButton.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> eventListener.onStickerPackClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void recycle() {
|
|
||||||
actionButton.setOnClickListener(null);
|
|
||||||
shareButton.setOnClickListener(null);
|
|
||||||
itemView.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final TextView titleView;
|
|
||||||
|
|
||||||
HeaderViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
|
|
||||||
this.titleView = itemView.findViewById(R.id.sticker_management_header);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(@StringRes int title) {
|
|
||||||
titleView.setText(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class EmptyViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final TextView text;
|
|
||||||
|
|
||||||
EmptyViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
|
|
||||||
this.text = itemView.findViewById(R.id.sticker_management_empty_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(@StringRes int title) {
|
|
||||||
text.setText(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventListener {
|
|
||||||
void onStickerPackClicked(@NonNull String packId, @NonNull String packKey);
|
|
||||||
void onStickerPackUninstallClicked(@NonNull String packId, @NonNull String packKey);
|
|
||||||
void onStickerPackInstallClicked(@NonNull String packId, @NonNull String packKey);
|
|
||||||
void onStickerPackShareClicked(@NonNull String packId, @NonNull String packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NoSectionException extends IllegalStateException {}
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase.StickerPackRecordReader;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
|
||||||
import org.session.libsession.utilities.concurrent.SignalExecutors;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
final class StickerManagementRepository {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final StickerDatabase stickerDatabase;
|
|
||||||
private final AttachmentDatabase attachmentDatabase;
|
|
||||||
|
|
||||||
StickerManagementRepository(@NonNull Context context) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.stickerDatabase = DatabaseFactory.getStickerDatabase(context);
|
|
||||||
this.attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteOrphanedStickerPacks() {
|
|
||||||
SignalExecutors.SERIAL.execute(stickerDatabase::deleteOrphanedPacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchUnretrievedReferencePacks() {
|
|
||||||
SignalExecutors.SERIAL.execute(() -> {
|
|
||||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
|
||||||
|
|
||||||
try (Cursor cursor = attachmentDatabase.getUnavailableStickerPacks()) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
String packId = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.STICKER_PACK_ID));
|
|
||||||
String packKey = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.STICKER_PACK_KEY));
|
|
||||||
|
|
||||||
jobManager.add(new StickerPackDownloadJob(packId, packKey, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void getStickerPacks(@NonNull Callback<PackResult> callback) {
|
|
||||||
SignalExecutors.SERIAL.execute(() -> {
|
|
||||||
List<StickerPackRecord> installedPacks = new ArrayList<>();
|
|
||||||
List<StickerPackRecord> availablePacks = new ArrayList<>();
|
|
||||||
List<StickerPackRecord> blessedPacks = new ArrayList<>();
|
|
||||||
|
|
||||||
try (StickerPackRecordReader reader = new StickerPackRecordReader(stickerDatabase.getAllStickerPacks())) {
|
|
||||||
StickerPackRecord record;
|
|
||||||
while ((record = reader.getNext()) != null) {
|
|
||||||
if (record.isInstalled()) {
|
|
||||||
installedPacks.add(record);
|
|
||||||
} else if (BlessedPacks.contains(record.getPackId())) {
|
|
||||||
blessedPacks.add(record);
|
|
||||||
} else {
|
|
||||||
availablePacks.add(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onComplete(new PackResult(installedPacks, availablePacks, blessedPacks));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void uninstallStickerPack(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
SignalExecutors.SERIAL.execute(() -> {
|
|
||||||
stickerDatabase.uninstallPack(packId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void installStickerPack(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
SignalExecutors.SERIAL.execute(() -> {
|
|
||||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
|
||||||
|
|
||||||
if (stickerDatabase.isPackAvailableAsReference(packId)) {
|
|
||||||
stickerDatabase.markPackAsInstalled(packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
jobManager.add(new StickerPackDownloadJob(packId, packKey, false));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static class PackResult {
|
|
||||||
|
|
||||||
private final List<StickerPackRecord> installedPacks;
|
|
||||||
private final List<StickerPackRecord> availablePacks;
|
|
||||||
private final List<StickerPackRecord> blessedPacks;
|
|
||||||
|
|
||||||
PackResult(@NonNull List<StickerPackRecord> installedPacks,
|
|
||||||
@NonNull List<StickerPackRecord> availablePacks,
|
|
||||||
@NonNull List<StickerPackRecord> blessedPacks)
|
|
||||||
{
|
|
||||||
this.installedPacks = installedPacks;
|
|
||||||
this.availablePacks = availablePacks;
|
|
||||||
this.blessedPacks = blessedPacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull List<StickerPackRecord> getInstalledPacks() {
|
|
||||||
return installedPacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull List<StickerPackRecord> getAvailablePacks() {
|
|
||||||
return availablePacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull List<StickerPackRecord> getBlessedPacks() {
|
|
||||||
return blessedPacks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback<T> {
|
|
||||||
void onComplete(T result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerManagementRepository.PackResult;
|
|
||||||
|
|
||||||
final class StickerManagementViewModel extends ViewModel {
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final StickerManagementRepository repository;
|
|
||||||
private final MutableLiveData<PackResult> packs;
|
|
||||||
private final ContentObserver observer;
|
|
||||||
|
|
||||||
private StickerManagementViewModel(@NonNull Application application, @NonNull StickerManagementRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
this.packs = new MutableLiveData<>();
|
|
||||||
this.observer = new ContentObserver(new Handler()) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
repository.deleteOrphanedStickerPacks();
|
|
||||||
repository.getStickerPacks(packs::postValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
repository.deleteOrphanedStickerPacks();
|
|
||||||
repository.fetchUnretrievedReferencePacks();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onVisible() {
|
|
||||||
repository.deleteOrphanedStickerPacks();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull LiveData<PackResult> getStickerPacks() {
|
|
||||||
repository.getStickerPacks(packs::postValue);
|
|
||||||
return packs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStickerPackUninstallClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
repository.uninstallStickerPack(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStickerPackInstallClicked(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
repository.installStickerPack(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
application.getContentResolver().unregisterContentObserver(observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final StickerManagementRepository repository;
|
|
||||||
|
|
||||||
Factory(@NonNull Application application, @NonNull StickerManagementRepository repository) {
|
|
||||||
this.application = application;
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return modelClass.cast(new StickerManagementViewModel(application, repository));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local model that represents the data present in the libsignal model
|
|
||||||
* {@link org.session.libsignal.service.api.messages.SignalServiceStickerManifest}.
|
|
||||||
*/
|
|
||||||
public final class StickerManifest {
|
|
||||||
|
|
||||||
private final String packId;
|
|
||||||
private final String packKey;
|
|
||||||
private final Optional<String> title;
|
|
||||||
private final Optional<String> author;
|
|
||||||
private final Optional<Sticker> cover;
|
|
||||||
private final List<Sticker> stickers;
|
|
||||||
|
|
||||||
public StickerManifest(@NonNull String packId,
|
|
||||||
@NonNull String packKey,
|
|
||||||
@NonNull Optional<String> title,
|
|
||||||
@NonNull Optional<String> author,
|
|
||||||
@NonNull Optional<Sticker> cover,
|
|
||||||
@NonNull List<Sticker> stickers)
|
|
||||||
{
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
this.title = title;
|
|
||||||
this.author = author;
|
|
||||||
this.cover = cover;
|
|
||||||
this.stickers = new ArrayList<>(stickers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackId() {
|
|
||||||
return packId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackKey() {
|
|
||||||
return packKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Optional<String> getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Optional<String> getAuthor() {
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Optional<Sticker> getCover() {
|
|
||||||
return cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull List<Sticker> getStickers() {
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Sticker {
|
|
||||||
private final String packId;
|
|
||||||
private final String packKey;
|
|
||||||
private final int id;
|
|
||||||
private final String emoji;
|
|
||||||
private final Optional<Uri> uri;
|
|
||||||
|
|
||||||
public Sticker(@NonNull String packId, @NonNull String packKey, int id, @NonNull String emoji) {
|
|
||||||
this(packId, packKey, id, emoji, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sticker(@NonNull String packId, @NonNull String packKey, int id, @NonNull String emoji, @Nullable Uri uri) {
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
this.id = id;
|
|
||||||
this.emoji = emoji;
|
|
||||||
this.uri = Optional.fromNullable(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackId() {
|
|
||||||
return packId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackKey() {
|
|
||||||
return packKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmoji() {
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Uri> getUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
public class StickerPackInstallEvent {
|
|
||||||
private final Object iconGlideModel;
|
|
||||||
|
|
||||||
public StickerPackInstallEvent(@NonNull Object iconGlideModel) {
|
|
||||||
this.iconGlideModel = iconGlideModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Object getIconGlideModel() {
|
|
||||||
return iconGlideModel;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,218 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.util.Pair;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
|
||||||
import org.thoughtcrime.securesms.ShareActivity;
|
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerManifest.Sticker;
|
|
||||||
import org.session.libsession.utilities.concurrent.SimpleTask;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the contents of a pack and allows the user to install it (if not installed) or remove it
|
|
||||||
* (if installed). This is also the handler for sticker pack deep links.
|
|
||||||
*/
|
|
||||||
public final class StickerPackPreviewActivity extends PassphraseRequiredActionBarActivity {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerPackPreviewActivity.class);
|
|
||||||
|
|
||||||
private StickerPackPreviewViewModel viewModel;
|
|
||||||
|
|
||||||
private ImageView coverImage;
|
|
||||||
private TextView stickerTitle;
|
|
||||||
private TextView stickerAuthor;
|
|
||||||
private View installButton;
|
|
||||||
private View removeButton;
|
|
||||||
private RecyclerView stickerList;
|
|
||||||
private View shareButton;
|
|
||||||
private View shareButtonImage;
|
|
||||||
|
|
||||||
private StickerPackPreviewAdapter adapter;
|
|
||||||
private GridLayoutManager layoutManager;
|
|
||||||
|
|
||||||
public static Intent getIntent(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, StickerUrl.createActionUri(packId, packKey));
|
|
||||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
|
||||||
setContentView(R.layout.sticker_preview_activity);
|
|
||||||
|
|
||||||
Optional<Pair<String, String>> stickerParams = StickerUrl.parseActionUri(getIntent().getData());
|
|
||||||
|
|
||||||
if (!stickerParams.isPresent()) {
|
|
||||||
Log.w(TAG, "Invalid URI!");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String packId = stickerParams.get().first();
|
|
||||||
String packKey = stickerParams.get().second();
|
|
||||||
|
|
||||||
initToolbar();
|
|
||||||
initView();
|
|
||||||
initViewModel(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
onScreenWidthChanged(getScreenWidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initView() {
|
|
||||||
this.coverImage = findViewById(R.id.sticker_install_cover);
|
|
||||||
this.stickerTitle = findViewById(R.id.sticker_install_title);
|
|
||||||
this.stickerAuthor = findViewById(R.id.sticker_install_author);
|
|
||||||
this.installButton = findViewById(R.id.sticker_install_button);
|
|
||||||
this.removeButton = findViewById(R.id.sticker_install_remove_button);
|
|
||||||
this.stickerList = findViewById(R.id.sticker_install_list);
|
|
||||||
this.shareButton = findViewById(R.id.sticker_install_share_button);
|
|
||||||
this.shareButtonImage = findViewById(R.id.sticker_install_share_button_image);
|
|
||||||
|
|
||||||
this.adapter = new StickerPackPreviewAdapter(GlideApp.with(this));
|
|
||||||
this.layoutManager = new GridLayoutManager(this, 2);
|
|
||||||
onScreenWidthChanged(getScreenWidth());
|
|
||||||
|
|
||||||
stickerList.setLayoutManager(layoutManager);
|
|
||||||
stickerList.setAdapter(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initToolbar() {
|
|
||||||
Toolbar toolbar = findViewById(R.id.sticker_install_toolbar);
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setTitle(R.string.StickerPackPreviewActivity_stickers);
|
|
||||||
|
|
||||||
toolbar.setNavigationOnClickListener(v -> onBackPressed());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViewModel(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
viewModel = ViewModelProviders.of(this, new StickerPackPreviewViewModel.Factory(getApplication(),
|
|
||||||
new StickerPackPreviewRepository(this),
|
|
||||||
new StickerManagementRepository(this)))
|
|
||||||
.get(StickerPackPreviewViewModel.class);
|
|
||||||
|
|
||||||
viewModel.getStickerManifest(packId, packKey).observe(this, manifest -> {
|
|
||||||
if (manifest == null) return;
|
|
||||||
|
|
||||||
if (manifest.isPresent()) {
|
|
||||||
presentManifest(manifest.get().getManifest());
|
|
||||||
presentButton(manifest.get().isInstalled());
|
|
||||||
presentShareButton(manifest.get().isInstalled(), manifest.get().getManifest().getPackId(), manifest.get().getManifest().getPackKey());
|
|
||||||
} else {
|
|
||||||
presentError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void presentManifest(@NonNull StickerManifest manifest) {
|
|
||||||
stickerTitle.setText(manifest.getTitle().or(getString(R.string.StickerPackPreviewActivity_untitled)));
|
|
||||||
stickerAuthor.setText(manifest.getAuthor().or(getString(R.string.StickerPackPreviewActivity_unknown)));
|
|
||||||
adapter.setStickers(manifest.getStickers());
|
|
||||||
|
|
||||||
installButton.setOnClickListener(v -> {
|
|
||||||
SimpleTask.run(() -> {
|
|
||||||
ApplicationContext.getInstance(this)
|
|
||||||
.getJobManager()
|
|
||||||
.add(new StickerPackDownloadJob(manifest.getPackId(), manifest.getPackKey(), false));
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, (nothing) -> finish());
|
|
||||||
});
|
|
||||||
|
|
||||||
Sticker first = manifest.getStickers().isEmpty() ? null : manifest.getStickers().get(0);
|
|
||||||
Sticker cover = manifest.getCover().or(Optional.fromNullable(first)).orNull();
|
|
||||||
|
|
||||||
if (cover != null) {
|
|
||||||
Object model = cover.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(cover.getUri().get())
|
|
||||||
: new StickerRemoteUri(cover.getPackId(), cover.getPackKey(), cover.getId());
|
|
||||||
GlideApp.with(this).load(model)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(coverImage);
|
|
||||||
} else {
|
|
||||||
coverImage.setImageDrawable(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void presentButton(boolean installed) {
|
|
||||||
if (installed) {
|
|
||||||
removeButton.setVisibility(View.VISIBLE);
|
|
||||||
removeButton.setOnClickListener(v -> {
|
|
||||||
viewModel.onRemoveClicked();
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
installButton.setVisibility(View.GONE);
|
|
||||||
installButton.setOnClickListener(null);
|
|
||||||
} else {
|
|
||||||
installButton.setVisibility(View.VISIBLE);
|
|
||||||
installButton.setOnClickListener(v -> {
|
|
||||||
viewModel.onInstallClicked();
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
removeButton.setVisibility(View.GONE);
|
|
||||||
removeButton.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void presentShareButton(boolean installed, @NonNull String packId, @NonNull String packKey) {
|
|
||||||
if (installed) {
|
|
||||||
shareButton.setVisibility(View.VISIBLE);
|
|
||||||
shareButtonImage.setVisibility(View.VISIBLE);
|
|
||||||
shareButton.setOnClickListener(v -> {
|
|
||||||
Intent composeIntent = new Intent(this, ShareActivity.class);
|
|
||||||
composeIntent.putExtra(Intent.EXTRA_TEXT, StickerUrl.createShareLink(packId, packKey));
|
|
||||||
startActivity(composeIntent);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
shareButton.setVisibility(View.GONE);
|
|
||||||
shareButtonImage.setVisibility(View.GONE);
|
|
||||||
shareButton.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void presentError() {
|
|
||||||
Toast.makeText(this, R.string.StickerPackPreviewActivity_failed_to_load_sticker_pack, Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onScreenWidthChanged(int newWidth) {
|
|
||||||
if (layoutManager != null) {
|
|
||||||
layoutManager.setSpanCount(newWidth / getResources().getDimensionPixelOffset(R.dimen.sticker_preview_sticker_size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getScreenWidth() {
|
|
||||||
Point size = new Point();
|
|
||||||
getWindowManager().getDefaultDisplay().getSize(size);
|
|
||||||
return size.x;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<StickerPackPreviewAdapter.StickerViewHolder> {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
private final List<StickerManifest.Sticker> list;
|
|
||||||
|
|
||||||
public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests) {
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
this.list = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull StickerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
|
||||||
return new StickerViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_preview_list_item, viewGroup, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull StickerViewHolder stickerViewHolder, int i) {
|
|
||||||
stickerViewHolder.bind(glideRequests, list.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStickers(List<StickerManifest.Sticker> stickers) {
|
|
||||||
list.clear();
|
|
||||||
list.addAll(stickers);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
static class StickerViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ImageView image;
|
|
||||||
|
|
||||||
private StickerViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
this.image = itemView.findViewById(R.id.sticker_install_item_image);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(@NonNull GlideRequests glideRequests, @NonNull StickerManifest.Sticker sticker) {
|
|
||||||
Object model = sticker.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(sticker.getUri().get())
|
|
||||||
: new StickerRemoteUri(sticker.getPackId(), sticker.getPackKey(), sticker.getId());
|
|
||||||
glideRequests.load(model)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.WorkerThread;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsession.utilities.concurrent.SignalExecutors;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceStickerManifest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public final class StickerPackPreviewRepository implements InjectableType {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerPackPreviewRepository.class);
|
|
||||||
|
|
||||||
private final StickerDatabase stickerDatabase;
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageReceiver receiver;
|
|
||||||
|
|
||||||
public StickerPackPreviewRepository(@NonNull Context context) {
|
|
||||||
ApplicationContext.getInstance(context).injectDependencies(this);
|
|
||||||
this.stickerDatabase = DatabaseFactory.getStickerDatabase(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getStickerManifest(@NonNull String packId,
|
|
||||||
@NonNull String packKey,
|
|
||||||
@NonNull Callback<Optional<StickerManifestResult>> callback)
|
|
||||||
{
|
|
||||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
|
||||||
Optional<StickerManifestResult> localManifest = getManifestFromDatabase(packId);
|
|
||||||
|
|
||||||
if (localManifest.isPresent()) {
|
|
||||||
Log.d(TAG, "Found manifest locally.");
|
|
||||||
callback.onComplete(localManifest);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "Looking for manifest remotely.");
|
|
||||||
callback.onComplete(getManifestRemote(packId, packKey));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private Optional<StickerManifestResult> getManifestFromDatabase(@NonNull String packId) {
|
|
||||||
StickerPackRecord record = stickerDatabase.getStickerPack(packId);
|
|
||||||
|
|
||||||
if (record != null && record.isInstalled()) {
|
|
||||||
StickerManifest.Sticker cover = toSticker(record.getCover());
|
|
||||||
List<StickerManifest.Sticker> stickers = getStickersFromDatabase(packId);
|
|
||||||
|
|
||||||
StickerManifest manifest = new StickerManifest(record.getPackId(),
|
|
||||||
record.getPackKey(),
|
|
||||||
record.getTitle(),
|
|
||||||
record.getAuthor(),
|
|
||||||
Optional.of(cover),
|
|
||||||
stickers);
|
|
||||||
|
|
||||||
return Optional.of(new StickerManifestResult(manifest, record.isInstalled()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private Optional<StickerManifestResult> getManifestRemote(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
try {
|
|
||||||
byte[] packIdBytes = Hex.fromStringCondensed(packId);
|
|
||||||
byte[] packKeyBytes = Hex.fromStringCondensed(packKey);
|
|
||||||
SignalServiceStickerManifest remoteManifest = receiver.retrieveStickerManifest(packIdBytes, packKeyBytes);
|
|
||||||
StickerManifest localManifest = new StickerManifest(packId,
|
|
||||||
packKey,
|
|
||||||
remoteManifest.getTitle(),
|
|
||||||
remoteManifest.getAuthor(),
|
|
||||||
toOptionalSticker(packId, packKey, remoteManifest.getCover()),
|
|
||||||
Stream.of(remoteManifest.getStickers())
|
|
||||||
.map(s -> toSticker(packId, packKey, s))
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
return Optional.of(new StickerManifestResult(localManifest, false));
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
|
||||||
Log.w(TAG, "Failed to retrieve pack manifest.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private List<StickerManifest.Sticker> getStickersFromDatabase(@NonNull String packId) {
|
|
||||||
List<StickerManifest.Sticker> stickers = new ArrayList<>();
|
|
||||||
|
|
||||||
try (Cursor cursor = stickerDatabase.getStickersForPack(packId)) {
|
|
||||||
StickerDatabase.StickerRecordReader reader = new StickerDatabase.StickerRecordReader(cursor);
|
|
||||||
|
|
||||||
StickerRecord record;
|
|
||||||
while ((record = reader.getNext()) != null) {
|
|
||||||
stickers.add(toSticker(record));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Optional<StickerManifest.Sticker> toOptionalSticker(@NonNull String packId,
|
|
||||||
@NonNull String packKey,
|
|
||||||
@NonNull Optional<SignalServiceStickerManifest.StickerInfo> remoteSticker)
|
|
||||||
{
|
|
||||||
return remoteSticker.isPresent() ? Optional.of(toSticker(packId, packKey, remoteSticker.get()))
|
|
||||||
: Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StickerManifest.Sticker toSticker(@NonNull String packId,
|
|
||||||
@NonNull String packKey,
|
|
||||||
@NonNull SignalServiceStickerManifest.StickerInfo remoteSticker)
|
|
||||||
{
|
|
||||||
return new StickerManifest.Sticker(packId, packKey, remoteSticker.getId(), remoteSticker.getEmoji());
|
|
||||||
}
|
|
||||||
|
|
||||||
private StickerManifest.Sticker toSticker(@NonNull StickerRecord record) {
|
|
||||||
return new StickerManifest.Sticker(record.getPackId(), record.getPackKey(), record.getStickerId(), record.getEmoji(), record.getUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
static class StickerManifestResult {
|
|
||||||
private final StickerManifest manifest;
|
|
||||||
private final boolean isInstalled;
|
|
||||||
|
|
||||||
StickerManifestResult(StickerManifest manifest, boolean isInstalled) {
|
|
||||||
this.manifest = manifest;
|
|
||||||
this.isInstalled = isInstalled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StickerManifest getManifest() {
|
|
||||||
return manifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInstalled() {
|
|
||||||
return isInstalled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback<T> {
|
|
||||||
void onComplete(T result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.os.Handler;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository.StickerManifestResult;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
final class StickerPackPreviewViewModel extends ViewModel {
|
|
||||||
|
|
||||||
private final Application application;
|
|
||||||
private final StickerPackPreviewRepository previewRepository;
|
|
||||||
private final StickerManagementRepository managementRepository;
|
|
||||||
private final MutableLiveData<Optional<StickerManifestResult>> stickerManifest;
|
|
||||||
private final ContentObserver packObserver;
|
|
||||||
|
|
||||||
private String packId;
|
|
||||||
private String packKey;
|
|
||||||
|
|
||||||
private StickerPackPreviewViewModel(@NonNull Application application,
|
|
||||||
@NonNull StickerPackPreviewRepository previewRepository,
|
|
||||||
@NonNull StickerManagementRepository managementRepository)
|
|
||||||
{
|
|
||||||
this.application = application;
|
|
||||||
this.previewRepository = previewRepository;
|
|
||||||
this.managementRepository = managementRepository;
|
|
||||||
this.stickerManifest = new MutableLiveData<>();
|
|
||||||
this.packObserver = new ContentObserver(new Handler()) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
if (!TextUtils.isEmpty(packId) && !TextUtils.isEmpty(packKey)) {
|
|
||||||
previewRepository.getStickerManifest(packId, packKey, stickerManifest::postValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<Optional<StickerManifestResult>> getStickerManifest(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
|
|
||||||
previewRepository.getStickerManifest(packId, packKey, stickerManifest::postValue);
|
|
||||||
|
|
||||||
return stickerManifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onInstallClicked() {
|
|
||||||
managementRepository.installStickerPack(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onRemoveClicked() {
|
|
||||||
managementRepository.uninstallStickerPack(packId, packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
application.getContentResolver().unregisterContentObserver(packObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
|
||||||
private final Application application;
|
|
||||||
private final StickerPackPreviewRepository previewRepository;
|
|
||||||
private final StickerManagementRepository managementRepository;
|
|
||||||
|
|
||||||
Factory(@NonNull Application application,
|
|
||||||
@NonNull StickerPackPreviewRepository previewRepository,
|
|
||||||
@NonNull StickerManagementRepository managementRepository)
|
|
||||||
{
|
|
||||||
this.application = application;
|
|
||||||
this.previewRepository = previewRepository;
|
|
||||||
this.managementRepository = managementRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return modelClass.cast(new StickerPackPreviewViewModel(application, previewRepository, managementRepository));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.PopupWindow;
|
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A popup that shows a given sticker fullscreen.
|
|
||||||
*/
|
|
||||||
final class StickerPreviewPopup extends PopupWindow {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
private final ImageView image;
|
|
||||||
|
|
||||||
StickerPreviewPopup(@NonNull Context context, @NonNull GlideRequests glideRequests) {
|
|
||||||
super(LayoutInflater.from(context).inflate(R.layout.sticker_preview_popup, null),
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
this.image = getContentView().findViewById(R.id.sticker_popup_image);
|
|
||||||
|
|
||||||
setTouchable(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void presentSticker(@NonNull StickerRecord stickerRecord) {
|
|
||||||
glideRequests.load(new DecryptableUri(stickerRecord.getUri()))
|
|
||||||
.into(image);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.Key;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used as a model to be given to Glide for a sticker that isn't present locally.
|
|
||||||
*/
|
|
||||||
public final class StickerRemoteUri implements Key {
|
|
||||||
|
|
||||||
private final String packId;
|
|
||||||
private final String packKey;
|
|
||||||
private final int stickerId;
|
|
||||||
|
|
||||||
public StickerRemoteUri(@NonNull String packId, @NonNull String packKey, int stickerId) {
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
this.stickerId = stickerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackId() {
|
|
||||||
return packId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String getPackKey() {
|
|
||||||
return packKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStickerId() {
|
|
||||||
return stickerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
|
||||||
messageDigest.update(packId.getBytes());
|
|
||||||
messageDigest.update(packKey.getBytes());
|
|
||||||
messageDigest.update(ByteBuffer.allocate(4).putInt(stickerId).array());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
StickerRemoteUri that = (StickerRemoteUri) o;
|
|
||||||
return stickerId == that.stickerId &&
|
|
||||||
Objects.equals(packId, that.packId) &&
|
|
||||||
Objects.equals(packKey, that.packKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(packId, packKey, stickerId);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.bumptech.glide.Priority;
|
|
||||||
import com.bumptech.glide.load.DataSource;
|
|
||||||
import com.bumptech.glide.load.data.DataFetcher;
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a sticker remotely. Used with Glide.
|
|
||||||
*/
|
|
||||||
public final class StickerRemoteUriFetcher implements DataFetcher<InputStream> {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(StickerRemoteUriFetcher.class);
|
|
||||||
|
|
||||||
private final SignalServiceMessageReceiver receiver;
|
|
||||||
private final StickerRemoteUri stickerUri;
|
|
||||||
|
|
||||||
public StickerRemoteUriFetcher(@NonNull SignalServiceMessageReceiver receiver, @NonNull StickerRemoteUri stickerUri) {
|
|
||||||
this.receiver = receiver;
|
|
||||||
this.stickerUri = stickerUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
|
|
||||||
try {
|
|
||||||
byte[] packIdBytes = Hex.fromStringCondensed(stickerUri.getPackId());
|
|
||||||
byte[] packKeyBytes = Hex.fromStringCondensed(stickerUri.getPackKey());
|
|
||||||
InputStream stream = receiver.retrieveSticker(packIdBytes, packKeyBytes, stickerUri.getStickerId());
|
|
||||||
|
|
||||||
callback.onDataReady(stream);
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
|
||||||
callback.onLoadFailed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
Log.d(TAG, "Canceled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Class<InputStream> getDataClass() {
|
|
||||||
return InputStream.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull DataSource getDataSource() {
|
|
||||||
return DataSource.REMOTE;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.Options;
|
|
||||||
import com.bumptech.glide.load.model.ModelLoader;
|
|
||||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
|
||||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Glide loader to fetch a sticker remotely.
|
|
||||||
*/
|
|
||||||
public final class StickerRemoteUriLoader implements ModelLoader<StickerRemoteUri, InputStream> {
|
|
||||||
|
|
||||||
private final SignalServiceMessageReceiver receiver;
|
|
||||||
|
|
||||||
public StickerRemoteUriLoader(@NonNull SignalServiceMessageReceiver receiver) {
|
|
||||||
this.receiver = receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable LoadData<InputStream> buildLoadData(@NonNull StickerRemoteUri sticker, int width, int height, @NonNull Options options) {
|
|
||||||
return new LoadData<>(sticker, new StickerRemoteUriFetcher(receiver, sticker));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handles(@NonNull StickerRemoteUri sticker) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Factory implements ModelLoaderFactory<StickerRemoteUri, InputStream>, InjectableType {
|
|
||||||
|
|
||||||
@Inject SignalServiceMessageReceiver receiver;
|
|
||||||
|
|
||||||
public Factory(@NonNull Context context) {
|
|
||||||
ApplicationContext.getInstance(context).injectDependencies(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull ModelLoader<StickerRemoteUri, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
|
|
||||||
return new StickerRemoteUriLoader(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void teardown() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.CursorList;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
|
||||||
import org.session.libsession.utilities.concurrent.SignalExecutors;
|
|
||||||
|
|
||||||
public final class StickerSearchRepository {
|
|
||||||
|
|
||||||
private final StickerDatabase stickerDatabase;
|
|
||||||
private final AttachmentDatabase attachmentDatabase;
|
|
||||||
|
|
||||||
public StickerSearchRepository(@NonNull Context context) {
|
|
||||||
this.stickerDatabase = DatabaseFactory.getStickerDatabase(context);
|
|
||||||
this.attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void searchByEmoji(@NonNull String emoji, @NonNull Callback<CursorList<StickerRecord>> callback) {
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
|
||||||
Cursor cursor = stickerDatabase.getStickersByEmoji(emoji);
|
|
||||||
|
|
||||||
if (cursor != null) {
|
|
||||||
callback.onResult(new CursorList<>(cursor, new StickerModelBuilder()));
|
|
||||||
} else {
|
|
||||||
callback.onResult(CursorList.emptyList());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getStickerFeatureAvailability(@NonNull Callback<Boolean> callback) {
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
|
||||||
try (Cursor cursor = stickerDatabase.getAllStickerPacks("1")) {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
callback.onResult(true);
|
|
||||||
} else {
|
|
||||||
callback.onResult(attachmentDatabase.hasStickerAttachments());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StickerModelBuilder implements CursorList.ModelBuilder<StickerRecord> {
|
|
||||||
@Override
|
|
||||||
public StickerRecord build(@NonNull Cursor cursor) {
|
|
||||||
return new StickerDatabase.StickerRecordReader(cursor).getCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StickerPackModelBuilder implements CursorList.ModelBuilder<StickerPackRecord> {
|
|
||||||
@Override
|
|
||||||
public StickerPackRecord build(@NonNull Cursor cursor) {
|
|
||||||
return new StickerDatabase.StickerPackRecordReader(cursor).getCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback<T> {
|
|
||||||
void onResult(T result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.stickers;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.util.Pair;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages creating and parsing the various sticker pack URLs.
|
|
||||||
*/
|
|
||||||
public class StickerUrl {
|
|
||||||
|
|
||||||
private static final Pattern STICKER_URL_PATTERN = Pattern.compile("^https://signal\\.org/addstickers/#pack_id=(.*)&pack_key=(.*)$");
|
|
||||||
|
|
||||||
public static Optional<Pair<String, String>> parseActionUri(@Nullable Uri uri) {
|
|
||||||
if (uri == null) return Optional.absent();
|
|
||||||
|
|
||||||
String packId = uri.getQueryParameter("pack_id");
|
|
||||||
String packKey = uri.getQueryParameter("pack_key");
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(packId) || TextUtils.isEmpty(packKey)) {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(new Pair<>(packId, packKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NonNull Uri createActionUri(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
return Uri.parse(String.format("sgnl://addstickers?pack_id=%s&pack_key=%s", packId, packKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isValidShareLink(@Nullable String url) {
|
|
||||||
return parseShareLink(url).isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NonNull Optional<Pair<String, String>> parseShareLink(@Nullable String url) {
|
|
||||||
if (url == null) return Optional.absent();
|
|
||||||
|
|
||||||
Matcher matcher = STICKER_URL_PATTERN.matcher(url);
|
|
||||||
|
|
||||||
if (matcher.matches() && matcher.groupCount() == 2) {
|
|
||||||
return Optional.of(new Pair<>(matcher.group(1), matcher.group(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String createShareLink(@NonNull String packId, @NonNull String packKey) {
|
|
||||||
return "https://signal.org/addstickers/#pack_id=" + packId + "&pack_key=" + packKey;
|
|
||||||
}
|
|
||||||
}
|
|
@ -116,10 +116,6 @@ object TextSecurePreferences {
|
|||||||
|
|
||||||
private const val GIF_GRID_LAYOUT = "pref_gif_grid_layout"
|
private const val GIF_GRID_LAYOUT = "pref_gif_grid_layout"
|
||||||
|
|
||||||
private const val SEEN_STICKER_INTRO_TOOLTIP = "pref_seen_sticker_intro_tooltip"
|
|
||||||
|
|
||||||
private const val MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode"
|
|
||||||
|
|
||||||
// region FCM
|
// region FCM
|
||||||
const val IS_USING_FCM = "pref_is_using_fcm"
|
const val IS_USING_FCM = "pref_is_using_fcm"
|
||||||
private const val FCM_TOKEN = "pref_fcm_token"
|
private const val FCM_TOKEN = "pref_fcm_token"
|
||||||
@ -764,27 +760,6 @@ object TextSecurePreferences {
|
|||||||
setIntegerPrefrence(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version)
|
setIntegerPrefrence(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun hasSeenStickerIntroTooltip(context: Context): Boolean {
|
|
||||||
return getBooleanPreference(context, SEEN_STICKER_INTRO_TOOLTIP, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setHasSeenStickerIntroTooltip(context: Context, seenStickerTooltip: Boolean) {
|
|
||||||
setBooleanPreference(context, SEEN_STICKER_INTRO_TOOLTIP, seenStickerTooltip)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setMediaKeyboardMode(context: Context, mode: MediaKeyboardMode) {
|
|
||||||
setStringPreference(context, MEDIA_KEYBOARD_MODE, mode.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getMediaKeyboardMode(context: Context): MediaKeyboardMode {
|
|
||||||
val name = getStringPreference(context, MEDIA_KEYBOARD_MODE, MediaKeyboardMode.EMOJI.name)!!
|
|
||||||
return MediaKeyboardMode.valueOf(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun setBooleanPreference(context: Context, key: String?, value: Boolean) {
|
fun setBooleanPreference(context: Context, key: String?, value: Boolean) {
|
||||||
getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply()
|
getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply()
|
||||||
|
@ -864,7 +864,7 @@ public class SignalServiceMessageSender {
|
|||||||
String body = Base64.encodeBytes(ciphertext);
|
String body = Base64.encodeBytes(ciphertext);
|
||||||
int type = isClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
int type = isClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE :
|
||||||
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||||
OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body);
|
OutgoingPushMessage message = new OutgoingPushMessage(type, body);
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
|
|
||||||
return new OutgoingPushMessageList(publicKey, timestamp, messages, false);
|
return new OutgoingPushMessageList(publicKey, timestamp, messages, false);
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* Licensed according to the LICENSE file in this repository.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.session.libsignal.service.internal.crypto;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
|
||||||
import org.session.libsignal.libsignal.ecc.Curve;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
|
||||||
import org.session.libsignal.libsignal.kdf.HKDFv3;
|
|
||||||
import org.session.libsignal.service.internal.util.Util;
|
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import static org.session.libsignal.service.internal.push.ProvisioningProtos.ProvisionEnvelope;
|
|
||||||
import static org.session.libsignal.service.internal.push.ProvisioningProtos.ProvisionMessage;
|
|
||||||
|
|
||||||
|
|
||||||
public class ProvisioningCipher {
|
|
||||||
|
|
||||||
private static final String TAG = ProvisioningCipher.class.getSimpleName();
|
|
||||||
|
|
||||||
private final ECPublicKey theirPublicKey;
|
|
||||||
|
|
||||||
public ProvisioningCipher(ECPublicKey theirPublicKey) {
|
|
||||||
this.theirPublicKey = theirPublicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encrypt(ProvisionMessage message) throws InvalidKeyException {
|
|
||||||
ECKeyPair ourKeyPair = Curve.generateKeyPair();
|
|
||||||
byte[] sharedSecret = Curve.calculateAgreement(theirPublicKey, ourKeyPair.getPrivateKey());
|
|
||||||
byte[] derivedSecret = new HKDFv3().deriveSecrets(sharedSecret, "TextSecure Provisioning Message".getBytes(), 64);
|
|
||||||
byte[][] parts = Util.split(derivedSecret, 32, 32);
|
|
||||||
|
|
||||||
byte[] version = {0x01};
|
|
||||||
byte[] ciphertext = getCiphertext(parts[0], message.toByteArray());
|
|
||||||
byte[] mac = getMac(parts[1], Util.join(version, ciphertext));
|
|
||||||
byte[] body = Util.join(version, ciphertext, mac);
|
|
||||||
|
|
||||||
return ProvisionEnvelope.newBuilder()
|
|
||||||
.setPublicKey(ByteString.copyFrom(ourKeyPair.getPublicKey().serialize()))
|
|
||||||
.setBody(ByteString.copyFrom(body))
|
|
||||||
.build()
|
|
||||||
.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getCiphertext(byte[] key, byte[] message) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
|
|
||||||
|
|
||||||
return Util.join(cipher.getIV(), cipher.doFinal(message));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (java.security.InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (IllegalBlockSizeException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (BadPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getMac(byte[] key, byte[] message) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
|
||||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
|
||||||
|
|
||||||
return mac.doFinal(message);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (java.security.InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -14,20 +14,11 @@ public class OutgoingPushMessage {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
public int type;
|
public int type;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int destinationDeviceId;
|
|
||||||
@JsonProperty
|
|
||||||
private int destinationRegistrationId;
|
|
||||||
@JsonProperty
|
|
||||||
public String content;
|
public String content;
|
||||||
|
|
||||||
public OutgoingPushMessage(int type,
|
public OutgoingPushMessage(int type, String content)
|
||||||
int destinationDeviceId,
|
|
||||||
int destinationRegistrationId,
|
|
||||||
String content)
|
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.destinationDeviceId = destinationDeviceId;
|
|
||||||
this.destinationRegistrationId = destinationRegistrationId;
|
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user