commit
5a248da445
@ -1,116 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.avatar;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import com.theartofdev.edmodo.cropper.CropImage;
|
|
||||||
import com.theartofdev.edmodo.cropper.CropImageView;
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.NoExternalStorageException;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.session.libsignal.utilities.ExternalStorageUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
import static android.provider.MediaStore.EXTRA_OUTPUT;
|
|
||||||
|
|
||||||
public final class AvatarSelection {
|
|
||||||
|
|
||||||
private static final String TAG = AvatarSelection.class.getSimpleName();
|
|
||||||
|
|
||||||
public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE;
|
|
||||||
public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1;
|
|
||||||
|
|
||||||
private AvatarSelection() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns result on {@link #REQUEST_CODE_CROP_IMAGE}
|
|
||||||
*/
|
|
||||||
public static void circularCropImage(Activity activity, Uri inputFile, Uri outputFile, @StringRes int title) {
|
|
||||||
CropImage.activity(inputFile)
|
|
||||||
.setGuidelines(CropImageView.Guidelines.ON)
|
|
||||||
.setAspectRatio(1, 1)
|
|
||||||
.setCropShape(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? CropImageView.CropShape.RECTANGLE : CropImageView.CropShape.OVAL)
|
|
||||||
.setOutputUri(outputFile)
|
|
||||||
.setAllowRotation(true)
|
|
||||||
.setAllowFlipping(true)
|
|
||||||
.setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background))
|
|
||||||
.setActivityTitle(activity.getString(title))
|
|
||||||
.start(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getResultUri(Intent data) {
|
|
||||||
return CropImage.getActivityResult(data).getUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns result on {@link #REQUEST_CODE_AVATAR}
|
|
||||||
*
|
|
||||||
* @return Temporary capture file if created.
|
|
||||||
*/
|
|
||||||
public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) {
|
|
||||||
File captureFile = null;
|
|
||||||
boolean hasCameraPermission = ContextCompat
|
|
||||||
.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
if (attemptToIncludeCamera && hasCameraPermission) {
|
|
||||||
try {
|
|
||||||
captureFile = File.createTempFile("avatar-capture", ".jpg", ExternalStorageUtil.getImageDir(activity));
|
|
||||||
} catch (IOException | NoExternalStorageException e) {
|
|
||||||
Log.e("Cannot reserve a temporary avatar capture file.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear);
|
|
||||||
activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR);
|
|
||||||
return captureFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) {
|
|
||||||
List<Intent> extraIntents = new LinkedList<>();
|
|
||||||
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
|
|
||||||
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
|
||||||
|
|
||||||
if (!IntentUtils.isResolvable(context, galleryIntent)) {
|
|
||||||
galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
galleryIntent.setType("image/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tempCaptureFile != null) {
|
|
||||||
Uri uri = FileProviderUtil.getUriFor(context, tempCaptureFile);
|
|
||||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
||||||
cameraIntent.putExtra(EXTRA_OUTPUT, uri);
|
|
||||||
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
||||||
extraIntents.add(cameraIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeClear) {
|
|
||||||
extraIntents.add(new Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.CreateProfileActivity_profile_photo));
|
|
||||||
|
|
||||||
if (!extraIntents.isEmpty()) {
|
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return chooserIntent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,141 @@
|
|||||||
|
package org.thoughtcrime.securesms.avatar
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.canhub.cropper.CropImageContractOptions
|
||||||
|
import com.canhub.cropper.CropImageOptions
|
||||||
|
import com.canhub.cropper.CropImageView
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
|
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.NoExternalStorageException
|
||||||
|
import org.thoughtcrime.securesms.util.FileProviderUtil
|
||||||
|
import org.thoughtcrime.securesms.util.IntentUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
class AvatarSelection(
|
||||||
|
private val activity: Activity,
|
||||||
|
private val onAvatarCropped: ActivityResultLauncher<CropImageContractOptions>,
|
||||||
|
private val onPickImage: ActivityResultLauncher<Intent>
|
||||||
|
) {
|
||||||
|
private val TAG: String = AvatarSelection::class.java.simpleName
|
||||||
|
|
||||||
|
private val bgColor by lazy { activity.getColorFromAttr(android.R.attr.colorPrimary) }
|
||||||
|
private val txtColor by lazy { activity.getColorFromAttr(android.R.attr.textColorPrimary) }
|
||||||
|
private val imageScrim by lazy { ContextCompat.getColor(activity, R.color.avatar_background) }
|
||||||
|
private val activityTitle by lazy { activity.getString(R.string.CropImageActivity_profile_avatar) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns result on [.REQUEST_CODE_CROP_IMAGE]
|
||||||
|
*/
|
||||||
|
fun circularCropImage(
|
||||||
|
inputFile: Uri?,
|
||||||
|
outputFile: Uri?
|
||||||
|
) {
|
||||||
|
onAvatarCropped.launch(
|
||||||
|
CropImageContractOptions(
|
||||||
|
uri = inputFile,
|
||||||
|
cropImageOptions = CropImageOptions(
|
||||||
|
guidelines = CropImageView.Guidelines.ON,
|
||||||
|
aspectRatioX = 1,
|
||||||
|
aspectRatioY = 1,
|
||||||
|
fixAspectRatio = true,
|
||||||
|
cropShape = CropImageView.CropShape.OVAL,
|
||||||
|
customOutputUri = outputFile,
|
||||||
|
allowRotation = true,
|
||||||
|
allowFlipping = true,
|
||||||
|
backgroundColor = imageScrim,
|
||||||
|
toolbarColor = bgColor,
|
||||||
|
activityBackgroundColor = bgColor,
|
||||||
|
toolbarTintColor = txtColor,
|
||||||
|
toolbarBackButtonColor = txtColor,
|
||||||
|
toolbarTitleColor = txtColor,
|
||||||
|
activityMenuIconColor = txtColor,
|
||||||
|
activityMenuTextColor = txtColor,
|
||||||
|
activityTitle = activityTitle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns result on [.REQUEST_CODE_AVATAR]
|
||||||
|
*
|
||||||
|
* @return Temporary capture file if created.
|
||||||
|
*/
|
||||||
|
fun startAvatarSelection(
|
||||||
|
includeClear: Boolean,
|
||||||
|
attemptToIncludeCamera: Boolean
|
||||||
|
): File? {
|
||||||
|
var captureFile: File? = null
|
||||||
|
val hasCameraPermission = ContextCompat
|
||||||
|
.checkSelfPermission(
|
||||||
|
activity,
|
||||||
|
Manifest.permission.CAMERA
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (attemptToIncludeCamera && hasCameraPermission) {
|
||||||
|
try {
|
||||||
|
captureFile = File.createTempFile("avatar-capture", ".jpg", getImageDir(activity))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
} catch (e: NoExternalStorageException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
||||||
|
onPickImage.launch(chooserIntent)
|
||||||
|
return captureFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAvatarSelectionIntent(
|
||||||
|
context: Context,
|
||||||
|
tempCaptureFile: File?,
|
||||||
|
includeClear: Boolean
|
||||||
|
): Intent {
|
||||||
|
val extraIntents: MutableList<Intent> = LinkedList()
|
||||||
|
var galleryIntent = Intent(Intent.ACTION_PICK)
|
||||||
|
galleryIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*")
|
||||||
|
|
||||||
|
if (!IntentUtils.isResolvable(context, galleryIntent)) {
|
||||||
|
galleryIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
galleryIntent.setType("image/*")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempCaptureFile != null) {
|
||||||
|
val uri = FileProviderUtil.getUriFor(context, tempCaptureFile)
|
||||||
|
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||||
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
|
||||||
|
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
extraIntents.add(cameraIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeClear) {
|
||||||
|
extraIntents.add(Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooserIntent = Intent.createChooser(
|
||||||
|
galleryIntent,
|
||||||
|
context.getString(R.string.CreateProfileActivity_profile_photo)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extraIntents.isEmpty()) {
|
||||||
|
chooserIntent.putExtra(
|
||||||
|
Intent.EXTRA_INITIAL_INTENTS,
|
||||||
|
extraIntents.toTypedArray<Intent>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chooserIntent
|
||||||
|
}
|
||||||
|
}
|
@ -1,198 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
|
|
||||||
import org.session.libsession.avatars.ContactColors;
|
|
||||||
import org.session.libsession.avatars.ContactPhoto;
|
|
||||||
import org.session.libsession.avatars.ResourceContactPhoto;
|
|
||||||
import org.session.libsession.utilities.Address;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.session.libsession.utilities.recipients.RecipientExporter;
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.RequestManager;
|
|
||||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class AvatarImageView extends AppCompatImageView {
|
|
||||||
|
|
||||||
private static final String TAG = AvatarImageView.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
|
|
||||||
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
|
|
||||||
|
|
||||||
static {
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 0, 0, 0));
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setAntiAlias(true);
|
|
||||||
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 255, 255, 255));
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setAntiAlias(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean inverted;
|
|
||||||
private Paint outlinePaint;
|
|
||||||
private OnClickListener listener;
|
|
||||||
|
|
||||||
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
|
||||||
private @NonNull Drawable unknownRecipientDrawable;
|
|
||||||
|
|
||||||
public AvatarImageView(Context context) {
|
|
||||||
super(context);
|
|
||||||
initialize(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarImageView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
initialize(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
||||||
setScaleType(ScaleType.CENTER_CROP);
|
|
||||||
|
|
||||||
if (attrs != null) {
|
|
||||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
|
||||||
inverted = typedArray.getBoolean(0, false);
|
|
||||||
typedArray.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
|
||||||
setOutlineProvider(new ViewOutlineProvider() {
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline) {
|
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setClipToOutline(true);
|
|
||||||
|
|
||||||
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
|
|
||||||
float width = getWidth() - getPaddingRight() - getPaddingLeft();
|
|
||||||
float height = getHeight() - getPaddingBottom() - getPaddingTop();
|
|
||||||
float cx = width / 2f;
|
|
||||||
float cy = height / 2f;
|
|
||||||
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
|
|
||||||
|
|
||||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
|
||||||
canvas.drawCircle(cx, cy, radius, outlinePaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnClickListener(OnClickListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(String hexEncodedPublicKey) {
|
|
||||||
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
|
||||||
Recipient recipient = Recipient.from(getContext(), address, false);
|
|
||||||
updateAvatar(recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAvatar(Recipient recipient) {
|
|
||||||
setAvatar(Glide.with(getContext()), recipient, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
|
||||||
if (recipient != null) {
|
|
||||||
if (recipient.isLocalNumber()) {
|
|
||||||
setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted));
|
|
||||||
} else {
|
|
||||||
RecipientContactPhoto photo = new RecipientContactPhoto(recipient);
|
|
||||||
if (!photo.equals(recipientContactPhoto)) {
|
|
||||||
requestManager.clear(this);
|
|
||||||
recipientContactPhoto = photo;
|
|
||||||
|
|
||||||
Drawable photoPlaceholderDrawable = AvatarPlaceholderGenerator.generate(
|
|
||||||
getContext(), 128, recipient.getAddress().serialize(), recipient.getName());
|
|
||||||
|
|
||||||
if (photo.contactPhoto != null) {
|
|
||||||
requestManager.load(photo.contactPhoto)
|
|
||||||
.fallback(photoPlaceholderDrawable)
|
|
||||||
.error(photoPlaceholderDrawable)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.circleCrop()
|
|
||||||
.into(this);
|
|
||||||
} else {
|
|
||||||
requestManager.load(photoPlaceholderDrawable)
|
|
||||||
.circleCrop()
|
|
||||||
.into(this);
|
|
||||||
// setImageDrawable(photoPlaceholderDrawable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recipientContactPhoto = null;
|
|
||||||
requestManager.clear(this);
|
|
||||||
setImageDrawable(unknownRecipientDrawable);
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear(@NonNull RequestManager glideRequests) {
|
|
||||||
glideRequests.clear(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) {
|
|
||||||
if (!recipient.isGroupRecipient() && quickContactEnabled) {
|
|
||||||
super.setOnClickListener(v -> {
|
|
||||||
if (recipient.getContactUri() != null) {
|
|
||||||
ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null);
|
|
||||||
} else {
|
|
||||||
getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RecipientContactPhoto {
|
|
||||||
|
|
||||||
private final @NonNull Recipient recipient;
|
|
||||||
private final @Nullable ContactPhoto contactPhoto;
|
|
||||||
private final boolean ready;
|
|
||||||
|
|
||||||
RecipientContactPhoto(@NonNull Recipient recipient) {
|
|
||||||
this.recipient = recipient;
|
|
||||||
this.ready = !recipient.isResolving();
|
|
||||||
this.contactPhoto = recipient.getContactPhoto();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(@Nullable RecipientContactPhoto other) {
|
|
||||||
if (other == null) return false;
|
|
||||||
|
|
||||||
return other.recipient.equals(recipient) &&
|
|
||||||
other.recipient.getColor().equals(recipient.getColor()) &&
|
|
||||||
other.ready == ready &&
|
|
||||||
Objects.equals(other.contactPhoto, contactPhoto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AttachmentDataSource implements DataSource {
|
|
||||||
|
|
||||||
private final DefaultDataSource defaultDataSource;
|
|
||||||
private final PartDataSource partDataSource;
|
|
||||||
|
|
||||||
private DataSource dataSource;
|
|
||||||
|
|
||||||
public AttachmentDataSource(DefaultDataSource defaultDataSource, PartDataSource partDataSource) {
|
|
||||||
this.defaultDataSource = defaultDataSource;
|
|
||||||
this.partDataSource = partDataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTransferListener(TransferListener transferListener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long open(DataSpec dataSpec) throws IOException {
|
|
||||||
if (PartAuthority.isLocalUri(dataSpec.uri)) dataSource = partDataSource;
|
|
||||||
else dataSource = defaultDataSource;
|
|
||||||
|
|
||||||
return dataSource.open(dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buffer, int offset, int readLength) throws IOException {
|
|
||||||
return dataSource.read(buffer, offset, readLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return dataSource.getUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> getResponseHeaders() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
dataSource.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
public class AttachmentDataSourceFactory implements DataSource.Factory {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
private final DefaultDataSourceFactory defaultDataSourceFactory;
|
|
||||||
private final TransferListener listener;
|
|
||||||
|
|
||||||
public AttachmentDataSourceFactory(@NonNull Context context,
|
|
||||||
@NonNull DefaultDataSourceFactory defaultDataSourceFactory,
|
|
||||||
@Nullable TransferListener listener)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
this.defaultDataSourceFactory = defaultDataSourceFactory;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AttachmentDataSource createDataSource() {
|
|
||||||
return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(),
|
|
||||||
new PartDataSource(context, listener));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
|
||||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class PartDataSource implements DataSource {
|
|
||||||
|
|
||||||
private final @NonNull Context context;
|
|
||||||
private final @Nullable TransferListener listener;
|
|
||||||
|
|
||||||
private Uri uri;
|
|
||||||
private InputStream inputSteam;
|
|
||||||
|
|
||||||
PartDataSource(@NonNull Context context, @Nullable TransferListener listener) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTransferListener(TransferListener transferListener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long open(DataSpec dataSpec) throws IOException {
|
|
||||||
this.uri = dataSpec.uri;
|
|
||||||
|
|
||||||
AttachmentDatabase attachmentDatabase = DatabaseComponent.get(context).attachmentDatabase();
|
|
||||||
PartUriParser partUri = new PartUriParser(uri);
|
|
||||||
Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
|
|
||||||
|
|
||||||
if (attachment == null) throw new IOException("Attachment not found");
|
|
||||||
|
|
||||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onTransferStart(this, dataSpec, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.getSize() - dataSpec.position <= 0) throw new EOFException("No more data");
|
|
||||||
|
|
||||||
return attachment.getSize() - dataSpec.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buffer, int offset, int readLength) throws IOException {
|
|
||||||
int read = inputSteam.read(buffer, offset, readLength);
|
|
||||||
|
|
||||||
if (read > 0 && listener != null) {
|
|
||||||
listener.onBytesTransferred(this, null, false, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> getResponseHeaders() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
inputSteam.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package org.session.libsession.avatars;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.amulyakhare.textdrawable.TextDrawable;
|
|
||||||
|
|
||||||
import org.session.libsession.R;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.ViewUtil;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class GeneratedContactPhoto implements FallbackContactPhoto {
|
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");
|
|
||||||
private static final Typeface TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final int fallbackResId;
|
|
||||||
|
|
||||||
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId) {
|
|
||||||
this.name = name;
|
|
||||||
this.fallbackResId = fallbackResId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Drawable asDrawable(Context context, int color) {
|
|
||||||
return asDrawable(context, color,false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
|
||||||
int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
|
|
||||||
String character = getAbbreviation(name);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(character)) {
|
|
||||||
Drawable base = TextDrawable.builder()
|
|
||||||
.beginConfig()
|
|
||||||
.width(targetSize)
|
|
||||||
.height(targetSize)
|
|
||||||
.useFont(TYPEFACE)
|
|
||||||
.fontSize(ViewUtil.dpToPx(context, 24))
|
|
||||||
.textColor(inverted ? color : Color.WHITE)
|
|
||||||
.endConfig()
|
|
||||||
.buildRound(character, inverted ? Color.WHITE : color);
|
|
||||||
|
|
||||||
Drawable gradient = context.getResources().getDrawable(ThemeUtil.isDarkTheme(context) ? R.drawable.avatar_gradient_dark
|
|
||||||
: R.drawable.avatar_gradient_light);
|
|
||||||
return new LayerDrawable(new Drawable[] { base, gradient });
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ResourceContactPhoto(fallbackResId).asDrawable(context, color, inverted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable String getAbbreviation(String name) {
|
|
||||||
String[] parts = name.split(" ");
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < parts.length && count < 2; i++) {
|
|
||||||
String cleaned = PATTERN.matcher(parts[i]).replaceFirst("");
|
|
||||||
if (!TextUtils.isEmpty(cleaned)) {
|
|
||||||
builder.appendCodePoint(cleaned.codePointAt(0));
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder.length() == 0) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,2 @@
|
|||||||
|
configurations.maybeCreate("default")
|
||||||
|
artifacts.add("default", file('stickyheadergrid-0.9.4.aar'))
|
Binary file not shown.
Loading…
Reference in New Issue