mirror of
https://github.com/oxen-io/session-android.git
synced 2023-12-14 02:53:01 +01:00
16ca97d2d3
* feat: Add emoji reacts support * Remove message multi-selection * Add emoji reaction model * Add emoji reaction panel * Blur reacts panel background * Show emoji keyboard * Add emoji sprites * Update reaction proto * Emoji database updates * Emoji database refactor * Emoji reaction persistence * Optimize reactions retrieval * Fix emoji group query * Display emojis * Fix emoji persistence * Cleanup * Persistence refactor * Add reactions bottom sheet * Cleanup * Ui tweaks * React with any emoji * Show emoji react notifications * Remove reaction * Show reactions modal on long press * Click to react (+1) with an emoji * Click to react with an emoji * Enable emoji expand/collapse * fix: some compile issues from merge conflicts * fix: compile issues merging quote and media message UI * fix: xml IDs and adding in legacy is selected for future inclusion * Fix view constraints * Fix merge issue * Add message selection option in conversation context menu * Add sogs emoji integration * Handle sogs emoji reactions * Enable sending/deleting sogs emojis * fix: improve the visible message layout * fix: add file IDs to request parameters for message send (#940) * Fix open group polling from seqno instead of last hash (#939) * fix: reset seqno to get recent messages from open groups * build: upgrade build numbers * fix: actually run the migration * Using StringBuilder to construct request url * Fix reaction filter * fix: is_mms added in second projection query * Update default emojis * fix: include legacy and new open groups in server ID tracking (#941) * feat: add hidden moderator and admin roles, separated as they may be used independently in future (#942) * Cleanup * Fix view constraints * Add reactions capability check * Fix reactions alignment * Ui fixes * Display reactions list * feat: add formatted count strings * fix: account for negatives and add tests * Migrate old official open group locations for polling and adding (#932) * feat: adding in first part of open group migrations and tests for migration logic / helpers * feat: test code and migration logic for open groups in the case of no conflicts * feat: add in extra test cases and refactor code for migrator * refactor: migrate open group join URLs and references to server in adding new open groups to catch legacy and re-write it * refactor: joining open groups using OpenGroupUrlParser.kt now * fix: add in compile issues for renamed OpenGroupApi.kt from OpenGroupV2 * fix: prevent duplicates of http/https for new open group DNS and prevent adding new groups based on public key * fix: room and server swapped parameters * fix: replace default server for config messages * fix: actually using public key to de-dupe didn't work for rooms * build: bump version code and name * Display reactions list on open groups for moderators * Ui tweaks * Ui tweaks for moderation * Refactor * fix: compile issue * fix: de-duping joined queries in the get X from cursor * Restore import * fix: colouring the reaction overlay scrubber * fix: highlight colour, show reaction count if 1 or above * Cleanup * fix: light mode accent * fix: light / dark mode themeing in reactions dialog fragment * Emoji notification blinded id check * fix: show reaction list correctly and pass isUserModerator to bind methods * fix: remove unnecessary places for the moderator * fix: X button for removing own react not showing up properly * feat: add clear all header view * fix: migrate the clear all to the correct location * fix: use display instead of base * Truncate emoji sender ids * feat: add notify thread function in thread db * Notify threads on reaction received * fix: design fixes for the reaction list * fix: emoji reactions bottom sheet dialog UI designs * feat: add unsupported emoji reaction * fix: crash and doing vector properly * Fix reaction database queries * Fix background open group adder job * Show new open group reactions * Fetch a maximum of 5 reactors * Handle open group reactions polling conflicts * Add count to user reaction * Show number of additional reactors * fix: unreads set same as the unread query * fix: design changes * fix: update dependency to improve flexboxlayout behaviour, design consistencies * Add select message icon and update long press menu items order and wording * Fix crash on reactors dialog * fix: colours and backgrounds to match designs * fix: add header in recipient item * fix: margins * fix: alignments and layout issues for emoji reactions view * feat: add overflow previews and logic for overflow * Dim action bar * Add emoji search * Search index fix * Set count for 1:1 and closed group reactions when inserting in local database * Use on screen toolbar to allow overlaying * Show/hide scroll to bottom button * feat: add extended properties so it doesn't collapse on re-bind * Cleanup * feat: prevent keeping extended on rebinding if we get a new message ID * fix: long press works on devices now, fix release lint issue and crash for emoji search DBs from emoji builds * Display message timestamp * Fix modal items alignment * fix: sort order and emoji count in compareTo * Scale down really large messages to fit * Prevent closed group crash * Fix reaction author Co-authored-by: charles <charles@oxen.io> Co-authored-by: jubb <hjubb@users.noreply.github.com>
381 lines
11 KiB
Java
381 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2011 Whisper Systems
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.thoughtcrime.securesms.conversation.v2;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.app.ActivityManager;
|
|
import android.content.ClipData;
|
|
import android.content.ClipboardManager;
|
|
import android.content.Context;
|
|
import android.graphics.Typeface;
|
|
import android.net.Uri;
|
|
import android.os.Build.VERSION;
|
|
import android.os.Build.VERSION_CODES;
|
|
import android.text.Spannable;
|
|
import android.text.SpannableString;
|
|
import android.text.TextUtils;
|
|
import android.text.style.StyleSpan;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.annimon.stream.Stream;
|
|
import com.google.android.mms.pdu_alt.CharacterSets;
|
|
import com.google.android.mms.pdu_alt.EncodedStringValue;
|
|
|
|
import org.session.libsignal.utilities.Log;
|
|
import org.thoughtcrime.securesms.components.ComposeText;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.security.SecureRandom;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import network.loki.messenger.R;
|
|
|
|
public class Util {
|
|
private static final String TAG = Log.tag(Util.class);
|
|
|
|
private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90);
|
|
|
|
public static <T> List<T> asList(T... elements) {
|
|
List<T> result = new LinkedList<>();
|
|
Collections.addAll(result, elements);
|
|
return result;
|
|
}
|
|
|
|
public static String join(String[] list, String delimiter) {
|
|
return join(Arrays.asList(list), delimiter);
|
|
}
|
|
|
|
public static <T> String join(Collection<T> list, String delimiter) {
|
|
StringBuilder result = new StringBuilder();
|
|
int i = 0;
|
|
|
|
for (T item : list) {
|
|
result.append(item);
|
|
|
|
if (++i < list.size())
|
|
result.append(delimiter);
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
public static String join(long[] list, String delimeter) {
|
|
List<Long> boxed = new ArrayList<>(list.length);
|
|
|
|
for (int i = 0; i < list.length; i++) {
|
|
boxed.add(list[i]);
|
|
}
|
|
|
|
return join(boxed, delimeter);
|
|
}
|
|
|
|
@SafeVarargs
|
|
public static @NonNull <E> List<E> join(@NonNull List<E>... lists) {
|
|
int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size());
|
|
List<E> joined = new ArrayList<>(totalSize);
|
|
|
|
for (List<E> list : lists) {
|
|
joined.addAll(list);
|
|
}
|
|
|
|
return joined;
|
|
}
|
|
|
|
public static String join(List<Long> list, String delimeter) {
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int j = 0; j < list.size(); j++) {
|
|
if (j != 0) sb.append(delimeter);
|
|
sb.append(list.get(j));
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
public static String rightPad(String value, int length) {
|
|
if (value.length() >= length) {
|
|
return value;
|
|
}
|
|
|
|
StringBuilder out = new StringBuilder(value);
|
|
while (out.length() < length) {
|
|
out.append(" ");
|
|
}
|
|
|
|
return out.toString();
|
|
}
|
|
|
|
public static boolean isEmpty(EncodedStringValue[] value) {
|
|
return value == null || value.length == 0;
|
|
}
|
|
|
|
public static boolean isEmpty(ComposeText value) {
|
|
return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed());
|
|
}
|
|
|
|
public static boolean isEmpty(Collection<?> collection) {
|
|
return collection == null || collection.isEmpty();
|
|
}
|
|
|
|
public static boolean isEmpty(@Nullable CharSequence charSequence) {
|
|
return charSequence == null || charSequence.length() == 0;
|
|
}
|
|
|
|
public static boolean hasItems(@Nullable Collection<?> collection) {
|
|
return collection != null && !collection.isEmpty();
|
|
}
|
|
|
|
public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
|
|
return map.containsKey(key) ? map.get(key) : defaultValue;
|
|
}
|
|
|
|
public static String getFirstNonEmpty(String... values) {
|
|
for (String value : values) {
|
|
if (!Util.isEmpty(value)) {
|
|
return value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public static @NonNull String emptyIfNull(@Nullable String value) {
|
|
return value != null ? value : "";
|
|
}
|
|
|
|
public static @NonNull CharSequence emptyIfNull(@Nullable CharSequence value) {
|
|
return value != null ? value : "";
|
|
}
|
|
|
|
public static CharSequence getBoldedString(String value) {
|
|
SpannableString spanned = new SpannableString(value);
|
|
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
|
|
spanned.length(),
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
return spanned;
|
|
}
|
|
|
|
public static @NonNull String toIsoString(byte[] bytes) {
|
|
try {
|
|
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new AssertionError("ISO_8859_1 must be supported!");
|
|
}
|
|
}
|
|
|
|
public static byte[] toIsoBytes(String isoString) {
|
|
try {
|
|
return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new AssertionError("ISO_8859_1 must be supported!");
|
|
}
|
|
}
|
|
|
|
public static byte[] toUtf8Bytes(String utf8String) {
|
|
try {
|
|
return utf8String.getBytes(CharacterSets.MIMENAME_UTF_8);
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new AssertionError("UTF_8 must be supported!");
|
|
}
|
|
}
|
|
|
|
public static void wait(Object lock, long timeout) {
|
|
try {
|
|
lock.wait(timeout);
|
|
} catch (InterruptedException ie) {
|
|
throw new AssertionError(ie);
|
|
}
|
|
}
|
|
|
|
public static List<String> split(String source, String delimiter) {
|
|
List<String> results = new LinkedList<>();
|
|
|
|
if (TextUtils.isEmpty(source)) {
|
|
return results;
|
|
}
|
|
|
|
String[] elements = source.split(delimiter);
|
|
Collections.addAll(results, elements);
|
|
|
|
return results;
|
|
}
|
|
|
|
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
|
|
byte[][] parts = new byte[2][];
|
|
|
|
parts[0] = new byte[firstLength];
|
|
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
|
|
|
parts[1] = new byte[secondLength];
|
|
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
|
|
|
return parts;
|
|
}
|
|
|
|
public static byte[] combine(byte[]... elements) {
|
|
try {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
for (byte[] element : elements) {
|
|
baos.write(element);
|
|
}
|
|
|
|
return baos.toByteArray();
|
|
} catch (IOException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static byte[] trim(byte[] input, int length) {
|
|
byte[] result = new byte[length];
|
|
System.arraycopy(input, 0, result, 0, result.length);
|
|
|
|
return result;
|
|
}
|
|
|
|
public static byte[] getSecretBytes(int size) {
|
|
return getSecretBytes(new SecureRandom(), size);
|
|
}
|
|
|
|
public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size) {
|
|
byte[] secret = new byte[size];
|
|
secureRandom.nextBytes(secret);
|
|
return secret;
|
|
}
|
|
|
|
public static <T> T getRandomElement(T[] elements) {
|
|
return elements[new SecureRandom().nextInt(elements.length)];
|
|
}
|
|
|
|
public static <T> T getRandomElement(List<T> elements) {
|
|
return elements.get(new SecureRandom().nextInt(elements.size()));
|
|
}
|
|
|
|
public static boolean equals(@Nullable Object a, @Nullable Object b) {
|
|
return a == b || (a != null && a.equals(b));
|
|
}
|
|
|
|
public static int hashCode(@Nullable Object... objects) {
|
|
return Arrays.hashCode(objects);
|
|
}
|
|
|
|
public static @Nullable Uri uri(@Nullable String uri) {
|
|
if (uri == null) return null;
|
|
else return Uri.parse(uri);
|
|
}
|
|
|
|
@TargetApi(VERSION_CODES.KITKAT)
|
|
public static boolean isLowMemory(Context context) {
|
|
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
|
|
return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) ||
|
|
activityManager.getLargeMemoryClass() <= 64;
|
|
}
|
|
|
|
public static int clamp(int value, int min, int max) {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|
|
|
|
public static long clamp(long value, long min, long max) {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|
|
|
|
public static float clamp(float value, float min, float max) {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|
|
|
|
/**
|
|
* Returns half of the difference between the given length, and the length when scaled by the
|
|
* given scale.
|
|
*/
|
|
public static float halfOffsetFromScale(int length, float scale) {
|
|
float scaledLength = length * scale;
|
|
return (length - scaledLength) / 2;
|
|
}
|
|
|
|
public static @Nullable String readTextFromClipboard(@NonNull Context context) {
|
|
{
|
|
ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
|
|
if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) {
|
|
return clipboardManager.getPrimaryClip().getItemAt(0).getText().toString();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) {
|
|
writeTextToClipboard(context, context.getString(R.string.app_name), text);
|
|
}
|
|
|
|
public static void writeTextToClipboard(@NonNull Context context, @NonNull String label, @NonNull String text) {
|
|
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
ClipData clip = ClipData.newPlainText(label, text);
|
|
clipboard.setPrimaryClip(clip);
|
|
}
|
|
|
|
public static int toIntExact(long value) {
|
|
if ((int)value != value) {
|
|
throw new ArithmeticException("integer overflow");
|
|
}
|
|
return (int)value;
|
|
}
|
|
|
|
public static boolean isEquals(@Nullable Long first, long second) {
|
|
return first != null && first == second;
|
|
}
|
|
|
|
@SafeVarargs
|
|
public static <T> List<T> concatenatedList(Collection <T>... items) {
|
|
final List<T> concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size()));
|
|
|
|
for (Collection<T> list : items) {
|
|
concat.addAll(list);
|
|
}
|
|
|
|
return concat;
|
|
}
|
|
|
|
public static boolean isLong(String value) {
|
|
try {
|
|
Long.parseLong(value);
|
|
return true;
|
|
} catch (NumberFormatException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static int parseInt(String integer, int defaultValue) {
|
|
try {
|
|
return Integer.parseInt(integer);
|
|
} catch (NumberFormatException e) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
}
|