mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add avatar picker and defaults.
This commit is contained in:
committed by
Greyson Parrelli
parent
0093e1d3eb
commit
ed23c3fe7c
@@ -154,6 +154,6 @@ public final class AvatarUtil {
|
||||
private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
String name = Optional.fromNullable(recipient.getDisplayName(context)).or("");
|
||||
|
||||
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40).asDrawable(context, recipient.getAvatarColor().colorInt());
|
||||
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40).asDrawable(context, recipient.getAvatarColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.Options;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
@@ -23,9 +22,8 @@ import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
@@ -186,8 +184,8 @@ public final class ConversationShortcutPhoto implements Key {
|
||||
photoSource = R.drawable.ic_profile_80;
|
||||
}
|
||||
|
||||
FallbackContactPhoto photo = recipient.isSelf() || recipient.isGroup() ? new FallbackPhoto80dp(photoSource, recipient.getAvatarColor().colorInt())
|
||||
: new ShortcutGeneratedContactPhoto(recipient.getDisplayName(context), photoSource, ViewUtil.dpToPx(80), ViewUtil.dpToPx(28), recipient.getAvatarColor().colorInt());
|
||||
FallbackContactPhoto photo = recipient.isSelf() || recipient.isGroup() ? new FallbackPhoto80dp(photoSource, recipient.getAvatarColor())
|
||||
: new ShortcutGeneratedContactPhoto(recipient.getDisplayName(context), photoSource, ViewUtil.dpToPx(80), recipient.getAvatarColor());
|
||||
Bitmap toWrap = DrawableUtil.toBitmap(photo.asCallCard(context), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80));
|
||||
Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap);
|
||||
|
||||
@@ -199,20 +197,20 @@ public final class ConversationShortcutPhoto implements Key {
|
||||
|
||||
private static final class ShortcutGeneratedContactPhoto extends GeneratedContactPhoto {
|
||||
|
||||
private final int color;
|
||||
private final AvatarColor color;
|
||||
|
||||
public ShortcutGeneratedContactPhoto(@NonNull String name, int fallbackResId, int targetSize, int fontSize, int color) {
|
||||
super(name, fallbackResId, targetSize, fontSize);
|
||||
public ShortcutGeneratedContactPhoto(@NonNull String name, int fallbackResId, int targetSize, @NonNull AvatarColor color) {
|
||||
super(name, fallbackResId, targetSize);
|
||||
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable newFallbackDrawable(@NonNull Context context, int color, boolean inverted) {
|
||||
return new FallbackPhoto80dp(getFallbackResId(), color).asDrawable(context, -1);
|
||||
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
|
||||
return new FallbackPhoto80dp(getFallbackResId(), color).asDrawable(context, AvatarColor.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override public Drawable asCallCard(Context context) {
|
||||
@Override public Drawable asCallCard(@NonNull Context context) {
|
||||
return new FallbackPhoto80dp(getFallbackResId(), color).asCallCard(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
@@ -33,16 +34,16 @@ import kotlin.jvm.functions.Function1;
|
||||
* override compiler typing recommendations when binding and diffing.
|
||||
* <p></p>
|
||||
* General pattern for implementation:
|
||||
* <ol>
|
||||
* <li>Create {@link MappingModel}s for the items in the list. These encapsulate data massaging methods for views to use and the diff logic.</li>
|
||||
* <li>Create {@link MappingViewHolder}s for each item type in the list and their corresponding {@link Factory}.</li>
|
||||
* <li>Create an instance or subclass of {@link MappingAdapter} and register the mapping of model type to view holder factory for that model type.</li>
|
||||
* </ol>
|
||||
* Event listeners, click or otherwise, are handled at the view holder level and should be passed into the appropriate view holder factories. This
|
||||
* pattern mimics how we pass data into view models via factories.
|
||||
* <p></p>
|
||||
* NOTE: There can only be on factory registered per model type. Registering two for the same type will result in the last one being used. However, the
|
||||
* same factory can be registered multiple times for multiple model types (if the model type class hierarchy supports it).
|
||||
* <ol>
|
||||
* <li>Create {@link MappingModel}s for the items in the list. These encapsulate data massaging methods for views to use and the diff logic.</li>
|
||||
* <li>Create {@link MappingViewHolder}s for each item type in the list and their corresponding {@link Factory}.</li>
|
||||
* <li>Create an instance or subclass of {@link MappingAdapter} and register the mapping of model type to view holder factory for that model type.</li>
|
||||
* </ol>
|
||||
* Event listeners, click or otherwise, are handled at the view holder level and should be passed into the appropriate view holder factories. This
|
||||
* pattern mimics how we pass data into view models via factories.
|
||||
* <p></p>
|
||||
* NOTE: There can only be on factory registered per model type. Registering two for the same type will result in the last one being used. However, the
|
||||
* same factory can be registered multiple times for multiple model types (if the model type class hierarchy supports it).
|
||||
*/
|
||||
public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHolder<?>> {
|
||||
|
||||
@@ -102,6 +103,12 @@ public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHold
|
||||
return Objects.requireNonNull(factories.get(viewType)).createViewHolder(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MappingViewHolder<?> holder, int position, @NonNull List<Object> payloads) {
|
||||
holder.setPayload(payloads);
|
||||
onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MappingViewHolder holder, int position) {
|
||||
//noinspection unchecked
|
||||
@@ -142,6 +149,16 @@ public class MappingAdapter extends ListAdapter<MappingModel<?>, MappingViewHold
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getChangePayload(@NonNull MappingModel oldItem, @NonNull MappingModel newItem) {
|
||||
if (oldItem.getClass() == newItem.getClass()) {
|
||||
//noinspection unchecked
|
||||
return oldItem.getChangePayload(newItem);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory<T extends MappingModel<T>> {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface MappingModel<T> {
|
||||
boolean areItemsTheSame(@NonNull T newItem);
|
||||
boolean areContentsTheSame(@NonNull T newItem);
|
||||
|
||||
default @Nullable Object getChangePayload(@NonNull T newItem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,18 @@ import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class MappingViewHolder<Model extends MappingModel<Model>> extends LifecycleViewHolder implements LifecycleOwner {
|
||||
|
||||
protected final Context context;
|
||||
protected final Context context;
|
||||
protected final List<Object> payload;
|
||||
|
||||
public MappingViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
context = itemView.getContext();
|
||||
payload = new LinkedList<>();
|
||||
}
|
||||
|
||||
public <T extends View> T findViewById(@IdRes int id) {
|
||||
@@ -26,6 +31,11 @@ public abstract class MappingViewHolder<Model extends MappingModel<Model>> exten
|
||||
|
||||
public abstract void bind(@NonNull Model model);
|
||||
|
||||
public void setPayload(@NonNull List<Object> payload) {
|
||||
this.payload.clear();
|
||||
this.payload.addAll(payload);
|
||||
}
|
||||
|
||||
public static final class SimpleViewHolder<Model extends MappingModel<Model>> extends MappingViewHolder<Model> {
|
||||
public SimpleViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.text.TextUtils
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object NameUtil {
|
||||
private val PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+")
|
||||
|
||||
/**
|
||||
* Returns an abbreviation of the input, up to two characters long.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getAbbreviation(name: String): String? {
|
||||
val parts = name.split(" ").toTypedArray()
|
||||
val builder = StringBuilder()
|
||||
var count = 0
|
||||
var i = 0
|
||||
|
||||
while (i < parts.size && count < 2) {
|
||||
val cleaned = PATTERN.matcher(parts[i]).replaceFirst("")
|
||||
if (!TextUtils.isEmpty(cleaned)) {
|
||||
builder.appendCodePoint(cleaned.codePointAt(0))
|
||||
count++
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return if (builder.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
builder.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.thoughtcrime.securesms.util.storage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Manages the storage of custom files.
|
||||
*/
|
||||
public final class FileStorage {
|
||||
|
||||
/**
|
||||
* Saves the provided input stream as a new file.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull String save(@NonNull Context context,
|
||||
@NonNull InputStream inputStream,
|
||||
@NonNull String directoryName,
|
||||
@NonNull String fileNameBase,
|
||||
@NonNull String extension
|
||||
) throws IOException
|
||||
{
|
||||
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
|
||||
File file = File.createTempFile(fileNameBase, "." + extension, directory);
|
||||
|
||||
StreamUtil.copy(inputStream, getOutputStream(context, file));
|
||||
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull InputStream read(@NonNull Context context,
|
||||
@NonNull String directoryName,
|
||||
@NonNull String filename) throws IOException
|
||||
{
|
||||
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
|
||||
File file = new File(directory, filename);
|
||||
|
||||
return getInputStream(context, file);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull List<String> getAll(@NonNull Context context,
|
||||
@NonNull String directoryName,
|
||||
@NonNull String fileNameBase)
|
||||
{
|
||||
return getAllFiles(context, directoryName, fileNameBase).stream()
|
||||
.map(File::getName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull List<File> getAllFiles(@NonNull Context context,
|
||||
@NonNull String directoryName,
|
||||
@NonNull String fileNameBase)
|
||||
{
|
||||
File directory = context.getDir(directoryName, Context.MODE_PRIVATE);
|
||||
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(fileNameBase));
|
||||
|
||||
if (allFiles != null) {
|
||||
return Arrays.asList(allFiles);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull OutputStream getOutputStream(@NonNull Context context, File outputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second;
|
||||
}
|
||||
|
||||
private static @NonNull InputStream getInputStream(@NonNull Context context, File inputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user