mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 14:18:26 +00:00
move Recipient to libsession
This commit is contained in:
parent
2dcbcee66c
commit
91ef23081b
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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.session.libsession.messaging.contacts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
|
||||
/**
|
||||
* This class was originally a layer of indirection between
|
||||
* ContactAccessorNewApi and ContactAccesorOldApi, which corresponded
|
||||
* to the API changes between 1.x and 2.x.
|
||||
*
|
||||
* Now that we no longer support 1.x, this class mostly serves as a place
|
||||
* to encapsulate Contact-related logic. It's still a singleton, mostly
|
||||
* just because that's how it's currently called from everywhere.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class ContactAccessor {
|
||||
|
||||
private static final ContactAccessor instance = new ContactAccessor();
|
||||
|
||||
public static synchronized ContactAccessor getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String getNameFromContact(Context context, Uri uri) {
|
||||
return "Anonymous";
|
||||
}
|
||||
|
||||
public ContactData getContactData(Context context, Uri uri) {
|
||||
return getContactData(context, getNameFromContact(context, uri), Long.parseLong(uri.getLastPathSegment()));
|
||||
}
|
||||
|
||||
private ContactData getContactData(Context context, String displayName, long id) {
|
||||
return new ContactData(id, displayName);
|
||||
}
|
||||
|
||||
public List<String> getNumbersForThreadSearchFilter(Context context, String constraint) {
|
||||
LinkedList<String> numberList = new LinkedList<>();
|
||||
|
||||
GroupDatabase.Reader reader = null;
|
||||
GroupRecord record;
|
||||
|
||||
try {
|
||||
reader = DatabaseFactory.getGroupDatabase(context).getGroupsFilteredByTitle(constraint);
|
||||
|
||||
while ((record = reader.getNext()) != null) {
|
||||
numberList.add(record.getEncodedId());
|
||||
}
|
||||
} finally {
|
||||
if (reader != null)
|
||||
reader.close();
|
||||
}
|
||||
|
||||
if (context.getString(R.string.note_to_self).toLowerCase().contains(constraint.toLowerCase()) &&
|
||||
!numberList.contains(TextSecurePreferences.getLocalNumber(context)))
|
||||
{
|
||||
numberList.add(TextSecurePreferences.getLocalNumber(context));
|
||||
}
|
||||
|
||||
return numberList;
|
||||
}
|
||||
|
||||
public CharSequence phoneTypeToString(Context mContext, int type, CharSequence label) {
|
||||
return label;
|
||||
}
|
||||
|
||||
public static class NumberData implements Parcelable {
|
||||
|
||||
public static final Creator<NumberData> CREATOR = new Creator<NumberData>() {
|
||||
public NumberData createFromParcel(Parcel in) {
|
||||
return new NumberData(in);
|
||||
}
|
||||
|
||||
public NumberData[] newArray(int size) {
|
||||
return new NumberData[size];
|
||||
}
|
||||
};
|
||||
|
||||
public final String number;
|
||||
public final String type;
|
||||
|
||||
public NumberData(String type, String number) {
|
||||
this.type = type;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public NumberData(Parcel in) {
|
||||
number = in.readString();
|
||||
type = in.readString();
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(number);
|
||||
dest.writeString(type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContactData implements Parcelable {
|
||||
|
||||
public static final Creator<ContactData> CREATOR = new Creator<ContactData>() {
|
||||
public ContactData createFromParcel(Parcel in) {
|
||||
return new ContactData(in);
|
||||
}
|
||||
|
||||
public ContactData[] newArray(int size) {
|
||||
return new ContactData[size];
|
||||
}
|
||||
};
|
||||
|
||||
public final long id;
|
||||
public final String name;
|
||||
public final List<NumberData> numbers;
|
||||
|
||||
public ContactData(long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.numbers = new LinkedList<NumberData>();
|
||||
}
|
||||
|
||||
public ContactData(Parcel in) {
|
||||
id = in.readLong();
|
||||
name = in.readString();
|
||||
numbers = new LinkedList<NumberData>();
|
||||
in.readTypedList(numbers, NumberData.CREATOR);
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(id);
|
||||
dest.writeString(name);
|
||||
dest.writeTypedList(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.messaging.contacts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
/**
|
||||
* CursorLoader that initializes a ContactsDatabase instance
|
||||
*
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ContactsCursorLoader extends CursorLoader {
|
||||
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
|
||||
|
||||
static final int NORMAL_TYPE = 0;
|
||||
static final int PUSH_TYPE = 1;
|
||||
static final int NEW_TYPE = 2;
|
||||
static final int RECENT_TYPE = 3;
|
||||
static final int DIVIDER_TYPE = 4;
|
||||
|
||||
static final String CONTACT_TYPE_COLUMN = "contact_type";
|
||||
static final String LABEL_COLUMN = "label";
|
||||
static final String NUMBER_TYPE_COLUMN = "number_type";
|
||||
static final String NUMBER_COLUMN = "number";
|
||||
static final String NAME_COLUMN = "name";
|
||||
|
||||
public static final class DisplayMode {
|
||||
public static final int FLAG_PUSH = 1;
|
||||
public static final int FLAG_SMS = 1 << 1;
|
||||
public static final int FLAG_GROUPS = 1 << 2;
|
||||
public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_GROUPS;
|
||||
}
|
||||
|
||||
private static final String[] CONTACT_PROJECTION = new String[]{NAME_COLUMN,
|
||||
NUMBER_COLUMN,
|
||||
NUMBER_TYPE_COLUMN,
|
||||
LABEL_COLUMN,
|
||||
CONTACT_TYPE_COLUMN};
|
||||
|
||||
private static final int RECENT_CONVERSATION_MAX = 25;
|
||||
|
||||
private final String filter;
|
||||
private final int mode;
|
||||
private final boolean recents;
|
||||
|
||||
public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
|
||||
{
|
||||
super(context);
|
||||
|
||||
this.filter = filter;
|
||||
this.mode = mode;
|
||||
this.recents = recents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
List<Cursor> cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
|
||||
: getFilteredResults();
|
||||
if (cursorList.size() > 0) {
|
||||
return new MergeCursor(cursorList.toArray(new Cursor[0]));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Cursor> getUnfilteredResults() {
|
||||
ArrayList<Cursor> cursorList = new ArrayList<>();
|
||||
|
||||
if (recents) {
|
||||
Cursor recentConversations = getRecentConversationsCursor();
|
||||
if (recentConversations.getCount() > 0) {
|
||||
cursorList.add(getRecentsHeaderCursor());
|
||||
cursorList.add(recentConversations);
|
||||
cursorList.add(getContactsHeaderCursor());
|
||||
}
|
||||
}
|
||||
cursorList.addAll(getContactsCursors());
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
private List<Cursor> getFilteredResults() {
|
||||
ArrayList<Cursor> cursorList = new ArrayList<>();
|
||||
|
||||
if (groupsEnabled(mode)) {
|
||||
Cursor groups = getGroupsCursor();
|
||||
if (groups.getCount() > 0) {
|
||||
List<Cursor> contacts = getContactsCursors();
|
||||
if (!isCursorListEmpty(contacts)) {
|
||||
cursorList.add(getContactsHeaderCursor());
|
||||
cursorList.addAll(contacts);
|
||||
cursorList.add(getGroupsHeaderCursor());
|
||||
}
|
||||
cursorList.add(groups);
|
||||
} else {
|
||||
cursorList.addAll(getContactsCursors());
|
||||
}
|
||||
} else {
|
||||
cursorList.addAll(getContactsCursors());
|
||||
}
|
||||
|
||||
if (NumberUtil.isValidSmsOrEmail(filter)) {
|
||||
cursorList.add(getNewNumberCursor());
|
||||
}
|
||||
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
private Cursor getRecentsHeaderCursor() {
|
||||
MatrixCursor recentsHeader = new MatrixCursor(CONTACT_PROJECTION);
|
||||
/*
|
||||
recentsHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_recent_chats),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactsDatabase.DIVIDER_TYPE });
|
||||
*/
|
||||
return recentsHeader;
|
||||
}
|
||||
|
||||
private Cursor getContactsHeaderCursor() {
|
||||
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
/*
|
||||
contactsHeader.addRow(new Object[] { getContext().getString(R.string.ContactsCursorLoader_contacts),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactsDatabase.DIVIDER_TYPE });
|
||||
*/
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
private Cursor getGroupsHeaderCursor() {
|
||||
MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
groupHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_groups),
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
DIVIDER_TYPE });
|
||||
return groupHeader;
|
||||
}
|
||||
|
||||
|
||||
private Cursor getRecentConversationsCursor() {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
|
||||
|
||||
MatrixCursor recentConversations = new MatrixCursor(CONTACT_PROJECTION, RECENT_CONVERSATION_MAX);
|
||||
try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX)) {
|
||||
ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations);
|
||||
ThreadRecord threadRecord;
|
||||
while ((threadRecord = reader.getNext()) != null) {
|
||||
recentConversations.addRow(new Object[] { threadRecord.getRecipient().toShortString(),
|
||||
threadRecord.getRecipient().getAddress().serialize(),
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
RECENT_TYPE });
|
||||
}
|
||||
}
|
||||
return recentConversations;
|
||||
}
|
||||
|
||||
private List<Cursor> getContactsCursors() {
|
||||
return new ArrayList<>(2);
|
||||
/*
|
||||
if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
if (pushEnabled(mode)) {
|
||||
cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
|
||||
}
|
||||
|
||||
if (pushEnabled(mode) && smsEnabled(mode)) {
|
||||
cursorList.add(contactsDatabase.querySystemContacts(filter));
|
||||
} else if (smsEnabled(mode)) {
|
||||
cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter)));
|
||||
}
|
||||
return cursorList;
|
||||
*/
|
||||
}
|
||||
|
||||
private Cursor getGroupsCursor() {
|
||||
MatrixCursor groupContacts = new MatrixCursor(CONTACT_PROJECTION);
|
||||
try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(filter)) {
|
||||
GroupDatabase.GroupRecord groupRecord;
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
groupContacts.addRow(new Object[] { groupRecord.getTitle(),
|
||||
groupRecord.getEncodedId(),
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"",
|
||||
NORMAL_TYPE });
|
||||
}
|
||||
}
|
||||
return groupContacts;
|
||||
}
|
||||
|
||||
private Cursor getNewNumberCursor() {
|
||||
MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1);
|
||||
newNumberCursor.addRow(new Object[] { getContext().getString(R.string.contact_selection_list__unknown_contact),
|
||||
filter,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"\u21e2",
|
||||
NEW_TYPE });
|
||||
return newNumberCursor;
|
||||
}
|
||||
|
||||
private static boolean isCursorListEmpty(List<Cursor> list) {
|
||||
int sum = 0;
|
||||
for (Cursor cursor : list) {
|
||||
sum += cursor.getCount();
|
||||
}
|
||||
return sum == 0;
|
||||
}
|
||||
|
||||
private static boolean pushEnabled(int mode) {
|
||||
return (mode & DisplayMode.FLAG_PUSH) > 0;
|
||||
}
|
||||
|
||||
private static boolean smsEnabled(int mode) {
|
||||
return (mode & DisplayMode.FLAG_SMS) > 0;
|
||||
}
|
||||
|
||||
private static boolean groupsEnabled(int mode) {
|
||||
return (mode & DisplayMode.FLAG_GROUPS) > 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class AvatarHelper {
|
||||
|
||||
private static final String AVATAR_DIRECTORY = "avatars";
|
||||
|
||||
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull Address address)
|
||||
throws IOException
|
||||
{
|
||||
return new FileInputStream(getAvatarFile(context, address));
|
||||
}
|
||||
|
||||
public static List<File> getAvatarFiles(@NonNull Context context) {
|
||||
File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
|
||||
File[] results = avatarDirectory.listFiles();
|
||||
|
||||
if (results == null) return new LinkedList<>();
|
||||
else return Stream.of(results).toList();
|
||||
}
|
||||
|
||||
public static void delete(@NonNull Context context, @NonNull Address address) {
|
||||
getAvatarFile(context, address).delete();
|
||||
}
|
||||
|
||||
public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull Address address) {
|
||||
File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
|
||||
avatarDirectory.mkdirs();
|
||||
|
||||
return new File(avatarDirectory, new File(address.serialize()).getName());
|
||||
}
|
||||
|
||||
public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
|
||||
throws IOException
|
||||
{
|
||||
if (data == null) {
|
||||
delete(context, address);
|
||||
} else {
|
||||
try (FileOutputStream out = new FileOutputStream(getAvatarFile(context, address))) {
|
||||
out.write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.utilities.color.MaterialColor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ContactColors {
|
||||
|
||||
public static final MaterialColor UNKNOWN_COLOR = MaterialColor.STEEL;
|
||||
|
||||
private static final List<MaterialColor> CONVERSATION_PALETTE = new ArrayList<>(Arrays.asList(
|
||||
MaterialColor.PLUM,
|
||||
MaterialColor.CRIMSON,
|
||||
MaterialColor.VERMILLION,
|
||||
MaterialColor.VIOLET,
|
||||
MaterialColor.BLUE,
|
||||
MaterialColor.INDIGO,
|
||||
MaterialColor.FOREST,
|
||||
MaterialColor.WINTERGREEN,
|
||||
MaterialColor.TEAL,
|
||||
MaterialColor.BURLAP,
|
||||
MaterialColor.TAUPE
|
||||
));
|
||||
|
||||
public static MaterialColor generateFor(@NonNull String name) {
|
||||
return CONVERSATION_PALETTE.get(Math.abs(name.hashCode()) % CONVERSATION_PALETTE.size());
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.utilities.color.MaterialColor;
|
||||
|
||||
|
||||
/**
|
||||
* Used for migrating legacy colors to modern colors. For normal color generation, use
|
||||
* {@link ContactColors}.
|
||||
*/
|
||||
public class ContactColorsLegacy {
|
||||
|
||||
private static final String[] LEGACY_PALETTE = new String[] {
|
||||
"red",
|
||||
"pink",
|
||||
"purple",
|
||||
"deep_purple",
|
||||
"indigo",
|
||||
"blue",
|
||||
"light_blue",
|
||||
"cyan",
|
||||
"teal",
|
||||
"green",
|
||||
"light_green",
|
||||
"orange",
|
||||
"deep_orange",
|
||||
"amber",
|
||||
"blue_grey"
|
||||
};
|
||||
|
||||
public static MaterialColor generateFor(@NonNull String name) {
|
||||
String serialized = LEGACY_PALETTE[Math.abs(name.hashCode()) % LEGACY_PALETTE.length];
|
||||
try {
|
||||
return MaterialColor.fromSerialized(serialized);
|
||||
} catch (MaterialColor.UnknownColorException e) {
|
||||
return ContactColors.generateFor(name);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.load.Key;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface ContactPhoto extends Key {
|
||||
|
||||
InputStream openInputStream(Context context) throws IOException;
|
||||
|
||||
@Nullable Uri getUri(@NonNull Context context);
|
||||
|
||||
boolean isProfilePhoto();
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public interface FallbackContactPhoto {
|
||||
|
||||
public Drawable asDrawable(Context context, int color);
|
||||
public Drawable asDrawable(Context context, int color, boolean inverted);
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package org.session.libsession.messaging.contacts.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 androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable asCallCard(Context context) {
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_person_large);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.StorageProtocol;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.GroupRecord;
|
||||
import org.session.libsession.utilities.Conversions;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class GroupRecordContactPhoto implements ContactPhoto {
|
||||
|
||||
private final @NonNull
|
||||
Address address;
|
||||
private final long avatarId;
|
||||
|
||||
public GroupRecordContactPhoto(@NonNull Address address, long avatarId) {
|
||||
this.address = address;
|
||||
this.avatarId = avatarId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream(Context context) throws IOException {
|
||||
StorageProtocol groupDatabase = MessagingConfiguration.shared.getStorage();
|
||||
Optional<GroupRecord> groupRecord = Optional.of(groupDatabase.getGroup(address.toGroupString()));
|
||||
|
||||
if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) {
|
||||
return new ByteArrayInputStream(groupRecord.get().getAvatar());
|
||||
}
|
||||
|
||||
throw new IOException("Couldn't load avatar for group: " + address.toGroupString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri(@NonNull Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProfilePhoto() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update(address.serialize().getBytes());
|
||||
messageDigest.update(Conversions.longToByteArray(avatarId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof GroupRecordContactPhoto)) return false;
|
||||
|
||||
GroupRecordContactPhoto that = (GroupRecordContactPhoto)other;
|
||||
return this.address.equals(that.address) && this.avatarId == that.avatarId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.address.hashCode() ^ (int) avatarId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class ProfileContactPhoto implements ContactPhoto {
|
||||
|
||||
private final @NonNull
|
||||
Address address;
|
||||
public final @NonNull String avatarObject;
|
||||
|
||||
public ProfileContactPhoto(@NonNull Address address, @NonNull String avatarObject) {
|
||||
this.address = address;
|
||||
this.avatarObject = avatarObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream(Context context) throws IOException {
|
||||
return AvatarHelper.getInputStreamFor(context, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri(@NonNull Context context) {
|
||||
return Uri.fromFile(AvatarHelper.getAvatarFile(context, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProfilePhoto() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update(address.serialize().getBytes());
|
||||
messageDigest.update(avatarObject.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof ProfileContactPhoto)) return false;
|
||||
|
||||
ProfileContactPhoto that = (ProfileContactPhoto)other;
|
||||
|
||||
return this.address.equals(that.address) && this.avatarObject.equals(that.avatarObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode() ^ avatarObject.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.makeramen.roundedimageview.RoundedDrawable;
|
||||
|
||||
|
||||
import org.session.libsession.R;
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
|
||||
public class ResourceContactPhoto implements FallbackContactPhoto {
|
||||
|
||||
private final int resourceId;
|
||||
private final int callCardResourceId;
|
||||
|
||||
public ResourceContactPhoto(@DrawableRes int resourceId) {
|
||||
this(resourceId, resourceId);
|
||||
}
|
||||
|
||||
public ResourceContactPhoto(@DrawableRes int resourceId, @DrawableRes int callCardResourceId) {
|
||||
this.resourceId = resourceId;
|
||||
this.callCardResourceId = callCardResourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable asDrawable(Context context, int color) {
|
||||
return asDrawable(context, color, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
||||
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
|
||||
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(resourceId));
|
||||
|
||||
foreground.setScaleType(ImageView.ScaleType.CENTER);
|
||||
|
||||
if (inverted) {
|
||||
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
Drawable gradient = context.getResources().getDrawable(ThemeUtil.isDarkTheme(context) ? R.drawable.avatar_gradient_dark
|
||||
: R.drawable.avatar_gradient_light);
|
||||
|
||||
return new ExpandingLayerDrawable(new Drawable[] {background, foreground, gradient});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable asCallCard(Context context) {
|
||||
return AppCompatResources.getDrawable(context, callCardResourceId);
|
||||
}
|
||||
|
||||
private static class ExpandingLayerDrawable extends LayerDrawable {
|
||||
public ExpandingLayerDrawable(Drawable[] layers) {
|
||||
super(layers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.utilities.Conversions;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class SystemContactPhoto implements ContactPhoto {
|
||||
|
||||
private final @NonNull
|
||||
Address address;
|
||||
private final @NonNull Uri contactPhotoUri;
|
||||
private final long lastModifiedTime;
|
||||
|
||||
public SystemContactPhoto(@NonNull Address address, @NonNull Uri contactPhotoUri, long lastModifiedTime) {
|
||||
this.address = address;
|
||||
this.contactPhotoUri = contactPhotoUri;
|
||||
this.lastModifiedTime = lastModifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream(Context context) throws FileNotFoundException {
|
||||
return context.getContentResolver().openInputStream(contactPhotoUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri(@NonNull Context context) {
|
||||
return contactPhotoUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProfilePhoto() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update(address.serialize().getBytes());
|
||||
messageDigest.update(contactPhotoUri.toString().getBytes());
|
||||
messageDigest.update(Conversions.longToByteArray(lastModifiedTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof SystemContactPhoto)) return false;
|
||||
|
||||
SystemContactPhoto that = (SystemContactPhoto)other;
|
||||
|
||||
return this.address.equals(that.address) && this.contactPhotoUri.equals(that.contactPhotoUri) && this.lastModifiedTime == that.lastModifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode() ^ contactPhotoUri.hashCode() ^ (int)lastModifiedTime;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package org.session.libsession.messaging.contacts.avatars;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.makeramen.roundedimageview.RoundedDrawable;
|
||||
|
||||
import org.session.libsession.R;
|
||||
|
||||
public class TransparentContactPhoto implements FallbackContactPhoto {
|
||||
|
||||
public TransparentContactPhoto() {}
|
||||
|
||||
@Override
|
||||
public Drawable asDrawable(Context context, int color) {
|
||||
return asDrawable(context, color, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
||||
return RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,919 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2013 - 2017 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.messaging.threads.recipients;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.function.Consumer;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.GroupRecord;
|
||||
import org.session.libsession.messaging.threads.recipients.RecipientProvider.RecipientDetails;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.color.MaterialColor;
|
||||
import org.session.libsignal.libsignal.logging.Log;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
||||
import org.session.libsession.messaging.contacts.avatars.ContactColors;
|
||||
import org.session.libsession.messaging.contacts.avatars.ContactPhoto;
|
||||
import org.session.libsession.messaging.contacts.avatars.GroupRecordContactPhoto;
|
||||
import org.session.libsession.messaging.contacts.avatars.ProfileContactPhoto;
|
||||
import org.session.libsession.messaging.contacts.avatars.SystemContactPhoto;
|
||||
import org.session.libsession.utilities.ProfilePictureModifiedEvent;
|
||||
import org.session.libsession.utilities.FutureTaskListener;
|
||||
import org.session.libsession.utilities.ListenableFutureTask;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.session.libsession.R;
|
||||
|
||||
public class Recipient implements RecipientModifiedListener {
|
||||
|
||||
private static final String TAG = Recipient.class.getSimpleName();
|
||||
private static final RecipientProvider provider = new RecipientProvider();
|
||||
|
||||
private final Set<RecipientModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientModifiedListener, Boolean>());
|
||||
|
||||
private final @NonNull Address address;
|
||||
private final @NonNull List<Recipient> participants = new LinkedList<>();
|
||||
|
||||
private Context context;
|
||||
private @Nullable String name;
|
||||
private @Nullable String customLabel;
|
||||
private boolean resolving;
|
||||
private boolean isLocalNumber;
|
||||
|
||||
private @Nullable Uri systemContactPhoto;
|
||||
private @Nullable Long groupAvatarId;
|
||||
private Uri contactUri;
|
||||
private @Nullable Uri messageRingtone = null;
|
||||
private @Nullable Uri callRingtone = null;
|
||||
public long mutedUntil = 0;
|
||||
private boolean blocked = false;
|
||||
private VibrateState messageVibrate = VibrateState.DEFAULT;
|
||||
private VibrateState callVibrate = VibrateState.DEFAULT;
|
||||
private int expireMessages = 0;
|
||||
private Optional<Integer> defaultSubscriptionId = Optional.absent();
|
||||
private @NonNull RegisteredState registered = RegisteredState.UNKNOWN;
|
||||
|
||||
private @Nullable MaterialColor color;
|
||||
private @Nullable byte[] profileKey;
|
||||
private @Nullable String profileName;
|
||||
private @Nullable String profileAvatar;
|
||||
private boolean profileSharing;
|
||||
private String notificationChannel;
|
||||
private boolean forceSmsSelection;
|
||||
|
||||
private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) {
|
||||
if (address == null) throw new AssertionError(address);
|
||||
return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, @NonNull Optional<RecipientSettings> settings, @NonNull Optional<GroupRecord> groupRecord, boolean asynchronous) {
|
||||
if (address == null) throw new AssertionError(address);
|
||||
return provider.getRecipient(context, address, settings, groupRecord, asynchronous);
|
||||
}
|
||||
|
||||
public static void applyCached(@NonNull Address address, Consumer<Recipient> consumer) {
|
||||
Optional<Recipient> recipient = provider.getCached(address);
|
||||
if (recipient.isPresent()) consumer.accept(recipient.get());
|
||||
}
|
||||
|
||||
public static boolean removeCached(@NonNull Address address) {
|
||||
return provider.removeCached(address);
|
||||
}
|
||||
|
||||
Recipient(@NonNull Context context,
|
||||
@NonNull Address address,
|
||||
@Nullable Recipient stale,
|
||||
@NonNull Optional<RecipientDetails> details,
|
||||
@NonNull ListenableFutureTask<RecipientDetails> future)
|
||||
{
|
||||
this.context = context;
|
||||
this.address = address;
|
||||
this.color = null;
|
||||
this.resolving = true;
|
||||
|
||||
if (stale != null) {
|
||||
this.name = stale.name;
|
||||
this.contactUri = stale.contactUri;
|
||||
this.systemContactPhoto = stale.systemContactPhoto;
|
||||
this.groupAvatarId = stale.groupAvatarId;
|
||||
this.isLocalNumber = stale.isLocalNumber;
|
||||
this.color = stale.color;
|
||||
this.customLabel = stale.customLabel;
|
||||
this.messageRingtone = stale.messageRingtone;
|
||||
this.callRingtone = stale.callRingtone;
|
||||
this.mutedUntil = stale.mutedUntil;
|
||||
this.blocked = stale.blocked;
|
||||
this.messageVibrate = stale.messageVibrate;
|
||||
this.callVibrate = stale.callVibrate;
|
||||
this.expireMessages = stale.expireMessages;
|
||||
this.defaultSubscriptionId = stale.defaultSubscriptionId;
|
||||
this.registered = stale.registered;
|
||||
this.notificationChannel = stale.notificationChannel;
|
||||
this.profileKey = stale.profileKey;
|
||||
this.profileName = stale.profileName;
|
||||
this.profileAvatar = stale.profileAvatar;
|
||||
this.profileSharing = stale.profileSharing;
|
||||
this.unidentifiedAccessMode = stale.unidentifiedAccessMode;
|
||||
this.forceSmsSelection = stale.forceSmsSelection;
|
||||
|
||||
this.participants.clear();
|
||||
this.participants.addAll(stale.participants);
|
||||
}
|
||||
|
||||
if (details.isPresent()) {
|
||||
this.name = details.get().name;
|
||||
this.systemContactPhoto = details.get().systemContactPhoto;
|
||||
this.groupAvatarId = details.get().groupAvatarId;
|
||||
this.isLocalNumber = details.get().isLocalNumber;
|
||||
this.color = details.get().color;
|
||||
this.messageRingtone = details.get().messageRingtone;
|
||||
this.callRingtone = details.get().callRingtone;
|
||||
this.mutedUntil = details.get().mutedUntil;
|
||||
this.blocked = details.get().blocked;
|
||||
this.messageVibrate = details.get().messageVibrateState;
|
||||
this.callVibrate = details.get().callVibrateState;
|
||||
this.expireMessages = details.get().expireMessages;
|
||||
this.defaultSubscriptionId = details.get().defaultSubscriptionId;
|
||||
this.registered = details.get().registered;
|
||||
this.notificationChannel = details.get().notificationChannel;
|
||||
this.profileKey = details.get().profileKey;
|
||||
this.profileName = details.get().profileName;
|
||||
this.profileAvatar = details.get().profileAvatar;
|
||||
this.profileSharing = details.get().profileSharing;
|
||||
this.unidentifiedAccessMode = details.get().unidentifiedAccessMode;
|
||||
this.forceSmsSelection = details.get().forceSmsSelection;
|
||||
|
||||
this.participants.clear();
|
||||
this.participants.addAll(details.get().participants);
|
||||
}
|
||||
|
||||
future.addListener(new FutureTaskListener<RecipientDetails>() {
|
||||
@Override
|
||||
public void onSuccess(RecipientDetails result) {
|
||||
if (result != null) {
|
||||
synchronized (Recipient.this) {
|
||||
Recipient.this.name = result.name;
|
||||
Recipient.this.contactUri = result.contactUri;
|
||||
Recipient.this.systemContactPhoto = result.systemContactPhoto;
|
||||
Recipient.this.groupAvatarId = result.groupAvatarId;
|
||||
Recipient.this.isLocalNumber = result.isLocalNumber;
|
||||
Recipient.this.color = result.color;
|
||||
Recipient.this.customLabel = result.customLabel;
|
||||
Recipient.this.messageRingtone = result.messageRingtone;
|
||||
Recipient.this.callRingtone = result.callRingtone;
|
||||
Recipient.this.mutedUntil = result.mutedUntil;
|
||||
Recipient.this.blocked = result.blocked;
|
||||
Recipient.this.messageVibrate = result.messageVibrateState;
|
||||
Recipient.this.callVibrate = result.callVibrateState;
|
||||
Recipient.this.expireMessages = result.expireMessages;
|
||||
Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId;
|
||||
Recipient.this.registered = result.registered;
|
||||
Recipient.this.notificationChannel = result.notificationChannel;
|
||||
Recipient.this.profileKey = result.profileKey;
|
||||
Recipient.this.profileName = result.profileName;
|
||||
Recipient.this.profileAvatar = result.profileAvatar;
|
||||
Recipient.this.profileSharing = result.profileSharing;
|
||||
Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode;
|
||||
Recipient.this.forceSmsSelection = result.forceSmsSelection;
|
||||
|
||||
Recipient.this.participants.clear();
|
||||
Recipient.this.participants.addAll(result.participants);
|
||||
Recipient.this.resolving = false;
|
||||
|
||||
if (!listeners.isEmpty()) {
|
||||
for (Recipient recipient : participants) recipient.addListener(Recipient.this);
|
||||
}
|
||||
|
||||
Recipient.this.notifyAll();
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException error) {
|
||||
Log.w(TAG, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Recipient(@NonNull Context context, @NonNull Address address, @NonNull RecipientDetails details) {
|
||||
this.context = context;
|
||||
this.address = address;
|
||||
this.contactUri = details.contactUri;
|
||||
this.name = details.name;
|
||||
this.systemContactPhoto = details.systemContactPhoto;
|
||||
this.groupAvatarId = details.groupAvatarId;
|
||||
this.isLocalNumber = details.isLocalNumber;
|
||||
this.color = details.color;
|
||||
this.customLabel = details.customLabel;
|
||||
this.messageRingtone = details.messageRingtone;
|
||||
this.callRingtone = details.callRingtone;
|
||||
this.mutedUntil = details.mutedUntil;
|
||||
this.blocked = details.blocked;
|
||||
this.messageVibrate = details.messageVibrateState;
|
||||
this.callVibrate = details.callVibrateState;
|
||||
this.expireMessages = details.expireMessages;
|
||||
this.defaultSubscriptionId = details.defaultSubscriptionId;
|
||||
this.registered = details.registered;
|
||||
this.notificationChannel = details.notificationChannel;
|
||||
this.profileKey = details.profileKey;
|
||||
this.profileName = details.profileName;
|
||||
this.profileAvatar = details.profileAvatar;
|
||||
this.profileSharing = details.profileSharing;
|
||||
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
|
||||
this.forceSmsSelection = details.forceSmsSelection;
|
||||
|
||||
this.participants.addAll(details.participants);
|
||||
this.resolving = false;
|
||||
}
|
||||
|
||||
public boolean isLocalNumber() {
|
||||
return isLocalNumber;
|
||||
}
|
||||
|
||||
public boolean isUserMasterDevice() {
|
||||
String userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
return userMasterDevice != null && userMasterDevice.equals(getAddress().serialize());
|
||||
}
|
||||
|
||||
public synchronized @Nullable Uri getContactUri() {
|
||||
return this.contactUri;
|
||||
}
|
||||
|
||||
public void setContactUri(@Nullable Uri contactUri) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(contactUri, this.contactUri)) {
|
||||
this.contactUri = contactUri;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getName() {
|
||||
String displayName = MessagingConfiguration.shared.getStorage().getDisplayName(this.address.toString());
|
||||
if (displayName != null) { return displayName; }
|
||||
|
||||
if (this.name == null && isMmsGroupRecipient()) {
|
||||
List<String> names = new LinkedList<>();
|
||||
|
||||
for (Recipient recipient : participants) {
|
||||
names.add(recipient.toShortString());
|
||||
}
|
||||
|
||||
return Util.join(names, ", ");
|
||||
}
|
||||
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(this.name, name)) {
|
||||
this.name = name;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @NonNull MaterialColor getColor() {
|
||||
if (isGroupRecipient()) return MaterialColor.GROUP;
|
||||
else if (color != null) return color;
|
||||
else if (name != null) return ContactColors.generateFor(name);
|
||||
else return ContactColors.UNKNOWN_COLOR;
|
||||
}
|
||||
|
||||
public void setColor(@NonNull MaterialColor color) {
|
||||
synchronized (this) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public @NonNull Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getCustomLabel() {
|
||||
return customLabel;
|
||||
}
|
||||
|
||||
public void setCustomLabel(@Nullable String customLabel) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(customLabel, this.customLabel)) {
|
||||
this.customLabel = customLabel;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized Optional<Integer> getDefaultSubscriptionId() {
|
||||
return defaultSubscriptionId;
|
||||
}
|
||||
|
||||
public void setDefaultSubscriptionId(Optional<Integer> defaultSubscriptionId) {
|
||||
synchronized (this) {
|
||||
this.defaultSubscriptionId = defaultSubscriptionId;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public void setProfileName(@Nullable String profileName) {
|
||||
synchronized (this) {
|
||||
this.profileName = profileName;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getProfileAvatar() {
|
||||
return profileAvatar;
|
||||
}
|
||||
|
||||
public void setProfileAvatar(@Nullable String profileAvatar) {
|
||||
synchronized (this) {
|
||||
this.profileAvatar = profileAvatar;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
EventBus.getDefault().post(new ProfilePictureModifiedEvent(this));
|
||||
}
|
||||
|
||||
public synchronized boolean isProfileSharing() {
|
||||
return profileSharing;
|
||||
}
|
||||
|
||||
public void setProfileSharing(boolean value) {
|
||||
synchronized (this) {
|
||||
this.profileSharing = value;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public boolean isGroupRecipient() {
|
||||
return address.isGroup();
|
||||
}
|
||||
|
||||
public boolean isOpenGroupRecipient() {
|
||||
return address.isOpenGroup();
|
||||
}
|
||||
|
||||
public boolean isMmsGroupRecipient() {
|
||||
return address.isMmsGroup();
|
||||
}
|
||||
|
||||
public boolean isPushGroupRecipient() {
|
||||
return address.isGroup() && !address.isMmsGroup();
|
||||
}
|
||||
|
||||
public @NonNull synchronized List<Recipient> getParticipants() {
|
||||
return new LinkedList<>(participants);
|
||||
}
|
||||
|
||||
public void setParticipants(@NonNull List<Recipient> participants) {
|
||||
synchronized (this) {
|
||||
this.participants.clear();
|
||||
this.participants.addAll(participants);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized void addListener(RecipientModifiedListener listener) {
|
||||
if (listeners.isEmpty()) {
|
||||
for (Recipient recipient : participants) recipient.addListener(this);
|
||||
}
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(RecipientModifiedListener listener) {
|
||||
listeners.remove(listener);
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
for (Recipient recipient : participants) recipient.removeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized String toShortString() {
|
||||
String name = getName();
|
||||
return (name != null ? name : address.serialize());
|
||||
}
|
||||
|
||||
public synchronized @Nullable ContactPhoto getContactPhoto() {
|
||||
if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context)));
|
||||
else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId);
|
||||
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0);
|
||||
else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar);
|
||||
else return null;
|
||||
}
|
||||
|
||||
public void setSystemContactPhoto(@Nullable Uri systemContactPhoto) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(systemContactPhoto, this.systemContactPhoto)) {
|
||||
this.systemContactPhoto = systemContactPhoto;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public void setGroupAvatarId(@Nullable Long groupAvatarId) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(this.groupAvatarId, groupAvatarId)) {
|
||||
this.groupAvatarId = groupAvatarId;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized Long getGroupAvatarId() {
|
||||
return groupAvatarId;
|
||||
}
|
||||
|
||||
public synchronized @Nullable Uri getMessageRingtone() {
|
||||
if (messageRingtone != null && messageRingtone.getScheme() != null && messageRingtone.getScheme().startsWith("file")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return messageRingtone;
|
||||
}
|
||||
|
||||
public void setMessageRingtone(@Nullable Uri ringtone) {
|
||||
synchronized (this) {
|
||||
this.messageRingtone = ringtone;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable Uri getCallRingtone() {
|
||||
if (callRingtone != null && callRingtone.getScheme() != null && callRingtone.getScheme().startsWith("file")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return callRingtone;
|
||||
}
|
||||
|
||||
public void setCallRingtone(@Nullable Uri ringtone) {
|
||||
synchronized (this) {
|
||||
this.callRingtone = ringtone;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isMuted() {
|
||||
return System.currentTimeMillis() <= mutedUntil;
|
||||
}
|
||||
|
||||
public void setMuted(long mutedUntil) {
|
||||
synchronized (this) {
|
||||
this.mutedUntil = mutedUntil;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isBlocked() {
|
||||
String masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(this.address.serialize());
|
||||
if (masterPublicKey != null) {
|
||||
return Recipient.from(context, Address.Companion.fromSerialized(masterPublicKey), false).blocked;
|
||||
} else {
|
||||
return blocked;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlocked(boolean blocked) {
|
||||
synchronized (this) {
|
||||
this.blocked = blocked;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized VibrateState getMessageVibrate() {
|
||||
return messageVibrate;
|
||||
}
|
||||
|
||||
public void setMessageVibrate(VibrateState vibrate) {
|
||||
synchronized (this) {
|
||||
this.messageVibrate = vibrate;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized VibrateState getCallVibrate() {
|
||||
return callVibrate;
|
||||
}
|
||||
|
||||
public void setCallVibrate(VibrateState vibrate) {
|
||||
synchronized (this) {
|
||||
this.callVibrate = vibrate;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized int getExpireMessages() {
|
||||
return expireMessages;
|
||||
}
|
||||
|
||||
public void setExpireMessages(int expireMessages) {
|
||||
synchronized (this) {
|
||||
this.expireMessages = expireMessages;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized RegisteredState getRegistered() {
|
||||
if (isPushGroupRecipient()) return RegisteredState.REGISTERED;
|
||||
else if (isMmsGroupRecipient()) return RegisteredState.NOT_REGISTERED;
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
public void setRegistered(@NonNull RegisteredState value) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (this.registered != value) {
|
||||
this.registered = value;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getNotificationChannel() {
|
||||
return !(Build.VERSION.SDK_INT >= 26) ? null : notificationChannel;
|
||||
}
|
||||
|
||||
public void setNotificationChannel(@Nullable String value) {
|
||||
boolean notify = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (!Util.equals(this.notificationChannel, value)) {
|
||||
this.notificationChannel = value;
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public boolean isForceSmsSelection() {
|
||||
return forceSmsSelection;
|
||||
}
|
||||
|
||||
public void setForceSmsSelection(boolean value) {
|
||||
synchronized (this) {
|
||||
this.forceSmsSelection = value;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @Nullable byte[] getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public void setProfileKey(@Nullable byte[] profileKey) {
|
||||
synchronized (this) {
|
||||
this.profileKey = profileKey;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public @NonNull synchronized UnidentifiedAccessMode getUnidentifiedAccessMode() {
|
||||
return unidentifiedAccessMode;
|
||||
}
|
||||
|
||||
public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
|
||||
synchronized (this) {
|
||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isSystemContact() {
|
||||
return contactUri != null;
|
||||
}
|
||||
|
||||
public synchronized Recipient resolve() {
|
||||
while (resolving) Util.wait(this, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof Recipient)) return false;
|
||||
|
||||
Recipient that = (Recipient) o;
|
||||
|
||||
return this.address.equals(that.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.address.hashCode();
|
||||
}
|
||||
|
||||
public void notifyListeners() {
|
||||
Set<RecipientModifiedListener> localListeners;
|
||||
|
||||
synchronized (this) {
|
||||
localListeners = new HashSet<>(listeners);
|
||||
}
|
||||
|
||||
for (RecipientModifiedListener listener : localListeners)
|
||||
listener.onModified(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isResolving() {
|
||||
return resolving;
|
||||
}
|
||||
|
||||
public enum VibrateState {
|
||||
DEFAULT(0), ENABLED(1), DISABLED(2);
|
||||
|
||||
private final int id;
|
||||
|
||||
VibrateState(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static VibrateState fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
|
||||
public enum RegisteredState {
|
||||
UNKNOWN(0), REGISTERED(1), NOT_REGISTERED(2);
|
||||
|
||||
private final int id;
|
||||
|
||||
RegisteredState(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static RegisteredState fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnidentifiedAccessMode {
|
||||
UNKNOWN(0), DISABLED(1), ENABLED(2), UNRESTRICTED(3);
|
||||
|
||||
private final int mode;
|
||||
|
||||
UnidentifiedAccessMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static UnidentifiedAccessMode fromMode(int mode) {
|
||||
return values()[mode];
|
||||
}
|
||||
}
|
||||
|
||||
public static class RecipientSettings {
|
||||
private final boolean blocked;
|
||||
private final long muteUntil;
|
||||
private final VibrateState messageVibrateState;
|
||||
private final VibrateState callVibrateState;
|
||||
private final Uri messageRingtone;
|
||||
private final Uri callRingtone;
|
||||
private final MaterialColor color;
|
||||
private final int defaultSubscriptionId;
|
||||
private final int expireMessages;
|
||||
private final RegisteredState registered;
|
||||
private final byte[] profileKey;
|
||||
private final String systemDisplayName;
|
||||
private final String systemContactPhoto;
|
||||
private final String systemPhoneLabel;
|
||||
private final String systemContactUri;
|
||||
private final String signalProfileName;
|
||||
private final String signalProfileAvatar;
|
||||
private final boolean profileSharing;
|
||||
private final String notificationChannel;
|
||||
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
private final boolean forceSmsSelection;
|
||||
|
||||
RecipientSettings(boolean blocked, long muteUntil,
|
||||
@NonNull VibrateState messageVibrateState,
|
||||
@NonNull VibrateState callVibrateState,
|
||||
@Nullable Uri messageRingtone,
|
||||
@Nullable Uri callRingtone,
|
||||
@Nullable MaterialColor color,
|
||||
int defaultSubscriptionId,
|
||||
int expireMessages,
|
||||
@NonNull RegisteredState registered,
|
||||
@Nullable byte[] profileKey,
|
||||
@Nullable String systemDisplayName,
|
||||
@Nullable String systemContactPhoto,
|
||||
@Nullable String systemPhoneLabel,
|
||||
@Nullable String systemContactUri,
|
||||
@Nullable String signalProfileName,
|
||||
@Nullable String signalProfileAvatar,
|
||||
boolean profileSharing,
|
||||
@Nullable String notificationChannel,
|
||||
@NonNull UnidentifiedAccessMode unidentifiedAccessMode,
|
||||
boolean forceSmsSelection)
|
||||
{
|
||||
this.blocked = blocked;
|
||||
this.muteUntil = muteUntil;
|
||||
this.messageVibrateState = messageVibrateState;
|
||||
this.callVibrateState = callVibrateState;
|
||||
this.messageRingtone = messageRingtone;
|
||||
this.callRingtone = callRingtone;
|
||||
this.color = color;
|
||||
this.defaultSubscriptionId = defaultSubscriptionId;
|
||||
this.expireMessages = expireMessages;
|
||||
this.registered = registered;
|
||||
this.profileKey = profileKey;
|
||||
this.systemDisplayName = systemDisplayName;
|
||||
this.systemContactPhoto = systemContactPhoto;
|
||||
this.systemPhoneLabel = systemPhoneLabel;
|
||||
this.systemContactUri = systemContactUri;
|
||||
this.signalProfileName = signalProfileName;
|
||||
this.signalProfileAvatar = signalProfileAvatar;
|
||||
this.profileSharing = profileSharing;
|
||||
this.notificationChannel = notificationChannel;
|
||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||
this.forceSmsSelection = forceSmsSelection;
|
||||
}
|
||||
|
||||
public @Nullable MaterialColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return muteUntil;
|
||||
}
|
||||
|
||||
public @NonNull VibrateState getMessageVibrateState() {
|
||||
return messageVibrateState;
|
||||
}
|
||||
|
||||
public @NonNull VibrateState getCallVibrateState() {
|
||||
return callVibrateState;
|
||||
}
|
||||
|
||||
public @Nullable Uri getMessageRingtone() {
|
||||
return messageRingtone;
|
||||
}
|
||||
|
||||
public @Nullable Uri getCallRingtone() {
|
||||
return callRingtone;
|
||||
}
|
||||
|
||||
public Optional<Integer> getDefaultSubscriptionId() {
|
||||
return defaultSubscriptionId != -1 ? Optional.of(defaultSubscriptionId) : Optional.absent();
|
||||
}
|
||||
|
||||
public int getExpireMessages() {
|
||||
return expireMessages;
|
||||
}
|
||||
|
||||
public RegisteredState getRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public @Nullable String getSystemDisplayName() {
|
||||
return systemDisplayName;
|
||||
}
|
||||
|
||||
public @Nullable String getSystemContactPhotoUri() {
|
||||
return systemContactPhoto;
|
||||
}
|
||||
|
||||
public @Nullable String getSystemPhoneLabel() {
|
||||
return systemPhoneLabel;
|
||||
}
|
||||
|
||||
public @Nullable String getSystemContactUri() {
|
||||
return systemContactUri;
|
||||
}
|
||||
|
||||
public @Nullable String getProfileName() {
|
||||
return signalProfileName;
|
||||
}
|
||||
|
||||
public @Nullable String getProfileAvatar() {
|
||||
return signalProfileAvatar;
|
||||
}
|
||||
|
||||
public boolean isProfileSharing() {
|
||||
return profileSharing;
|
||||
}
|
||||
|
||||
public @Nullable String getNotificationChannel() {
|
||||
return notificationChannel;
|
||||
}
|
||||
|
||||
public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() {
|
||||
return unidentifiedAccessMode;
|
||||
}
|
||||
|
||||
public boolean isForceSmsSelection() {
|
||||
return forceSmsSelection;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.session.libsession.messaging.threads.recipients;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
|
||||
import static android.content.Intent.ACTION_INSERT_OR_EDIT;
|
||||
|
||||
public final class RecipientExporter {
|
||||
|
||||
public static RecipientExporter export(Recipient recipient) {
|
||||
return new RecipientExporter(recipient);
|
||||
}
|
||||
|
||||
private final Recipient recipient;
|
||||
|
||||
private RecipientExporter(Recipient recipient) {
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public Intent asAddContactIntent() {
|
||||
Intent intent = new Intent(ACTION_INSERT_OR_EDIT);
|
||||
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
||||
addNameToIntent(intent, recipient.getProfileName());
|
||||
addAddressToIntent(intent, recipient.getAddress());
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static void addNameToIntent(Intent intent, String profileName) {
|
||||
if (!TextUtils.isEmpty(profileName)) {
|
||||
intent.putExtra(ContactsContract.Intents.Insert.NAME, profileName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAddressToIntent(Intent intent, Address address) {
|
||||
if (address.isPhone()) {
|
||||
intent.putExtra(ContactsContract.Intents.Insert.PHONE, address.toPhoneString());
|
||||
} else if (address.isEmail()) {
|
||||
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address.toEmailString());
|
||||
} else {
|
||||
throw new RuntimeException("Cannot export Recipient with neither phone nor email");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.session.libsession.messaging.threads.recipients;
|
||||
|
||||
public class RecipientFormattingException extends Exception {
|
||||
public RecipientFormattingException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public RecipientFormattingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RecipientFormattingException(String message, Throwable nested) {
|
||||
super(message, nested);
|
||||
}
|
||||
|
||||
public RecipientFormattingException(Throwable nested) {
|
||||
super(nested);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package org.session.libsession.messaging.threads.recipients;
|
||||
|
||||
|
||||
public interface RecipientModifiedListener {
|
||||
public void onModified(Recipient recipient);
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.session.libsession.messaging.threads.recipients;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsession.utilities.color.MaterialColor;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.GroupRecord;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSettings;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient.RegisteredState;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient.UnidentifiedAccessMode;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient.VibrateState;
|
||||
import org.session.libsession.utilities.ListenableFutureTask;
|
||||
import org.session.libsession.utilities.SoftHashMap;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.session.libsession.R;
|
||||
|
||||
class RecipientProvider {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = RecipientProvider.class.getSimpleName();
|
||||
|
||||
private static final RecipientCache recipientCache = new RecipientCache();
|
||||
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
|
||||
|
||||
private static final Map<String, RecipientDetails> STATIC_DETAILS = new HashMap<String, RecipientDetails>() {{
|
||||
put("262966", new RecipientDetails("Amazon", null, false, false, null, null));
|
||||
}};
|
||||
|
||||
@NonNull Recipient getRecipient(@NonNull Context context, @NonNull Address address, @NonNull Optional<RecipientSettings> settings, @NonNull Optional<GroupRecord> groupRecord, boolean asynchronous) {
|
||||
Recipient cachedRecipient = recipientCache.get(address);
|
||||
|
||||
if (cachedRecipient != null && (asynchronous || !cachedRecipient.isResolving()) && ((!groupRecord.isPresent() && !settings.isPresent()) || !cachedRecipient.isResolving() || cachedRecipient.getName() != null)) {
|
||||
return cachedRecipient;
|
||||
}
|
||||
|
||||
Optional<RecipientDetails> prefetchedRecipientDetails = createPrefetchedRecipientDetails(context, address, settings, groupRecord);
|
||||
|
||||
if (asynchronous) {
|
||||
cachedRecipient = new Recipient(context, address, cachedRecipient, prefetchedRecipientDetails, getRecipientDetailsAsync(context, address, settings, groupRecord));
|
||||
} else {
|
||||
cachedRecipient = new Recipient(context, address, getRecipientDetailsSync(context, address, settings, groupRecord, false));
|
||||
}
|
||||
|
||||
recipientCache.set(address, cachedRecipient);
|
||||
return cachedRecipient;
|
||||
}
|
||||
|
||||
@NonNull Optional<Recipient> getCached(@NonNull Address address) {
|
||||
return Optional.fromNullable(recipientCache.get(address));
|
||||
}
|
||||
|
||||
boolean removeCached(@NonNull Address address) {
|
||||
return recipientCache.remove(address);
|
||||
}
|
||||
|
||||
private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address,
|
||||
@NonNull Optional<RecipientSettings> settings,
|
||||
@NonNull Optional<GroupRecord> groupRecord)
|
||||
{
|
||||
if (address.isGroup() && settings.isPresent() && groupRecord.isPresent()) {
|
||||
return Optional.of(getGroupRecipientDetails(context, address, groupRecord, settings, true));
|
||||
} else if (!address.isGroup() && settings.isPresent()) {
|
||||
boolean isLocalNumber = address.serialize().equals(TextSecurePreferences.getLocalNumber(context));
|
||||
return Optional.of(new RecipientDetails(null, null, !TextUtils.isEmpty(settings.get().getSystemDisplayName()), isLocalNumber, settings.get(), null));
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
private @NonNull ListenableFutureTask<RecipientDetails> getRecipientDetailsAsync(final Context context, final @NonNull Address address, final @NonNull Optional<RecipientSettings> settings, final @NonNull Optional<GroupRecord> groupRecord)
|
||||
{
|
||||
Callable<RecipientDetails> task = () -> getRecipientDetailsSync(context, address, settings, groupRecord, true);
|
||||
|
||||
ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<>(task);
|
||||
asyncRecipientResolver.submit(future);
|
||||
return future;
|
||||
}
|
||||
|
||||
private @NonNull RecipientDetails getRecipientDetailsSync(Context context, @NonNull Address address, Optional<RecipientSettings> settings, Optional<GroupRecord> groupRecord, boolean nestedAsynchronous) {
|
||||
if (address.isGroup()) return getGroupRecipientDetails(context, address, groupRecord, settings, nestedAsynchronous);
|
||||
else return getIndividualRecipientDetails(context, address, settings);
|
||||
}
|
||||
|
||||
private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, @NonNull Address address, Optional<RecipientSettings> settings) {
|
||||
if (!settings.isPresent()) {
|
||||
settings = Optional.of(MessagingConfiguration.shared.getStorage().getRecipientSettings(address));
|
||||
}
|
||||
|
||||
if (!settings.isPresent() && STATIC_DETAILS.containsKey(address.serialize())) {
|
||||
return STATIC_DETAILS.get(address.serialize());
|
||||
} else {
|
||||
boolean systemContact = settings.isPresent() && !TextUtils.isEmpty(settings.get().getSystemDisplayName());
|
||||
boolean isLocalNumber = address.serialize().equals(TextSecurePreferences.getLocalNumber(context));
|
||||
return new RecipientDetails(null, null, systemContact, isLocalNumber, settings.orNull(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional<GroupRecord> groupRecord, Optional<RecipientSettings> settings, boolean asynchronous) {
|
||||
|
||||
if (!groupRecord.isPresent()) {
|
||||
groupRecord = Optional.of(MessagingConfiguration.shared.getStorage().getGroup(groupId.toGroupString()));
|
||||
}
|
||||
|
||||
if (!settings.isPresent()) {
|
||||
settings = Optional.of(MessagingConfiguration.shared.getStorage().getRecipientSettings(groupId));
|
||||
}
|
||||
|
||||
if (groupRecord.isPresent()) {
|
||||
String title = groupRecord.get().getTitle();
|
||||
List<Address> memberAddresses = groupRecord.get().getMembers();
|
||||
List<Recipient> members = new LinkedList<>();
|
||||
Long avatarId = null;
|
||||
|
||||
for (Address memberAddress : memberAddresses) {
|
||||
members.add(getRecipient(context, memberAddress, Optional.absent(), Optional.absent(), asynchronous));
|
||||
}
|
||||
|
||||
if (!groupId.isMmsGroup() && title == null) {
|
||||
title = context.getString(R.string.RecipientProvider_unnamed_group);
|
||||
}
|
||||
|
||||
if (groupRecord.get().getAvatar() != null && groupRecord.get().getAvatar().length > 0) {
|
||||
avatarId = groupRecord.get().getAvatarId();
|
||||
}
|
||||
|
||||
return new RecipientDetails(title, avatarId, false, false, settings.orNull(), members);
|
||||
}
|
||||
|
||||
return new RecipientDetails(context.getString(R.string.RecipientProvider_unnamed_group), null, false, false, settings.orNull(), null);
|
||||
}
|
||||
|
||||
static class RecipientDetails {
|
||||
@Nullable final String name;
|
||||
@Nullable final String customLabel;
|
||||
@Nullable final Uri systemContactPhoto;
|
||||
@Nullable final Uri contactUri;
|
||||
@Nullable final Long groupAvatarId;
|
||||
@Nullable final MaterialColor color;
|
||||
@Nullable final Uri messageRingtone;
|
||||
@Nullable final Uri callRingtone;
|
||||
final long mutedUntil;
|
||||
@Nullable final VibrateState messageVibrateState;
|
||||
@Nullable final VibrateState callVibrateState;
|
||||
final boolean blocked;
|
||||
final int expireMessages;
|
||||
@NonNull final List<Recipient> participants;
|
||||
@Nullable final String profileName;
|
||||
final Optional<Integer> defaultSubscriptionId;
|
||||
@NonNull final RegisteredState registered;
|
||||
@Nullable final byte[] profileKey;
|
||||
@Nullable final String profileAvatar;
|
||||
final boolean profileSharing;
|
||||
final boolean systemContact;
|
||||
final boolean isLocalNumber;
|
||||
@Nullable final String notificationChannel;
|
||||
@NonNull final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
final boolean forceSmsSelection;
|
||||
|
||||
RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId,
|
||||
boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings,
|
||||
@Nullable List<Recipient> participants)
|
||||
{
|
||||
this.groupAvatarId = groupAvatarId;
|
||||
this.systemContactPhoto = settings != null ? Util.uri(settings.getSystemContactPhotoUri()) : null;
|
||||
this.customLabel = settings != null ? settings.getSystemPhoneLabel() : null;
|
||||
this.contactUri = settings != null ? Util.uri(settings.getSystemContactUri()) : null;
|
||||
this.color = settings != null ? settings.getColor() : null;
|
||||
this.messageRingtone = settings != null ? settings.getMessageRingtone() : null;
|
||||
this.callRingtone = settings != null ? settings.getCallRingtone() : null;
|
||||
this.mutedUntil = settings != null ? settings.getMuteUntil() : 0;
|
||||
this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null;
|
||||
this.callVibrateState = settings != null ? settings.getCallVibrateState() : null;
|
||||
this.blocked = settings != null && settings.isBlocked();
|
||||
this.expireMessages = settings != null ? settings.getExpireMessages() : 0;
|
||||
this.participants = participants == null ? new LinkedList<>() : participants;
|
||||
this.profileName = settings != null ? settings.getProfileName() : null;
|
||||
this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent();
|
||||
this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN;
|
||||
this.profileKey = settings != null ? settings.getProfileKey() : null;
|
||||
this.profileAvatar = settings != null ? settings.getProfileAvatar() : null;
|
||||
this.profileSharing = settings != null && settings.isProfileSharing();
|
||||
this.systemContact = systemContact;
|
||||
this.isLocalNumber = isLocalNumber;
|
||||
this.notificationChannel = settings != null ? settings.getNotificationChannel() : null;
|
||||
this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED;
|
||||
this.forceSmsSelection = settings != null && settings.isForceSmsSelection();
|
||||
|
||||
if (name == null && settings != null) this.name = settings.getSystemDisplayName();
|
||||
else this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecipientCache {
|
||||
|
||||
private final Map<Address,Recipient> cache = new SoftHashMap<>(1000);
|
||||
|
||||
public synchronized Recipient get(Address address) {
|
||||
return cache.get(address);
|
||||
}
|
||||
|
||||
public synchronized void set(Address address, Recipient recipient) {
|
||||
cache.put(address, recipient);
|
||||
}
|
||||
|
||||
public synchronized boolean remove(Address address) {
|
||||
return cache.remove(address) != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.session.libsession.messaging.threads.recipients;
|
||||
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class RecipientsFormatter {
|
||||
|
||||
private static String parseBracketedNumber(String recipient) throws RecipientFormattingException {
|
||||
int begin = recipient.indexOf('<');
|
||||
int end = recipient.indexOf('>');
|
||||
String value = recipient.substring(begin + 1, end);
|
||||
|
||||
if (PhoneNumberUtils.isWellFormedSmsAddress(value))
|
||||
return value;
|
||||
else
|
||||
throw new RecipientFormattingException("Bracketed value: " + value + " is not valid.");
|
||||
}
|
||||
|
||||
private static String parseRecipient(String recipient) throws RecipientFormattingException {
|
||||
recipient = recipient.trim();
|
||||
|
||||
if ((recipient.indexOf('<') != -1) && (recipient.indexOf('>') != -1))
|
||||
return parseBracketedNumber(recipient);
|
||||
|
||||
if (PhoneNumberUtils.isWellFormedSmsAddress(recipient))
|
||||
return recipient;
|
||||
|
||||
throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted.");
|
||||
}
|
||||
|
||||
public static List<String> getRecipients(String rawText) throws RecipientFormattingException {
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
results.add(parseRecipient(tokenizer.nextToken()));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static String formatNameAndNumber(String name, String number) {
|
||||
// Format like this: Mike Cleron <(650) 555-1234>
|
||||
// Erick Tseng <(650) 555-1212>
|
||||
// Tutankhamun <tutank1341@gmail.com>
|
||||
// (408) 555-1289
|
||||
String formattedNumber = PhoneNumberUtils.formatNumber(number);
|
||||
if (!TextUtils.isEmpty(name) && !name.equals(number)) {
|
||||
return name + " <" + formattedNumber + ">";
|
||||
} else {
|
||||
return formattedNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
public class Conversions {
|
||||
|
||||
public static byte intsToByteHighAndLow(int highValue, int lowValue) {
|
||||
return (byte)((highValue << 4 | lowValue) & 0xFF);
|
||||
}
|
||||
|
||||
public static int highBitsToInt(byte value) {
|
||||
return (value & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
public static int lowBitsToInt(byte value) {
|
||||
return (value & 0xF);
|
||||
}
|
||||
|
||||
public static int highBitsToMedium(int value) {
|
||||
return (value >> 12);
|
||||
}
|
||||
|
||||
public static int lowBitsToMedium(int value) {
|
||||
return (value & 0xFFF);
|
||||
}
|
||||
|
||||
public static byte[] shortToByteArray(int value) {
|
||||
byte[] bytes = new byte[2];
|
||||
shortToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset+1] = (byte)value;
|
||||
bytes[offset] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static byte[] mediumToByteArray(int value) {
|
||||
byte[] bytes = new byte[3];
|
||||
mediumToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 2] = (byte)value;
|
||||
bytes[offset + 1] = (byte)(value >> 8);
|
||||
bytes[offset] = (byte)(value >> 16);
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static byte[] intToByteArray(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
intToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
bytes[offset+2] = (byte)(value >> 16);
|
||||
bytes[offset+3] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte[] longToByteArray(long l) {
|
||||
byte[] bytes = new byte[8];
|
||||
longToByteArray(bytes, 0, l);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 7] = (byte)value;
|
||||
bytes[offset + 6] = (byte)(value >> 8);
|
||||
bytes[offset + 5] = (byte)(value >> 16);
|
||||
bytes[offset + 4] = (byte)(value >> 24);
|
||||
bytes[offset + 3] = (byte)(value >> 32);
|
||||
bytes[offset + 2] = (byte)(value >> 40);
|
||||
bytes[offset + 1] = (byte)(value >> 48);
|
||||
bytes[offset] = (byte)(value >> 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset + 0] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes) {
|
||||
return byteArrayToShort(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff);
|
||||
}
|
||||
|
||||
// The SSL patented 3-byte Value.
|
||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset + 2] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes) {
|
||||
return byteArrayToInt(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 24 |
|
||||
(bytes[offset + 1] & 0xff) << 16 |
|
||||
(bytes[offset + 2] & 0xff) << 8 |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToIntLittleEndian(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset + 3] & 0xff) << 24 |
|
||||
(bytes[offset + 2] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset] & 0xff);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
return byteArrayToLong(bytes, 0);
|
||||
}
|
||||
|
||||
public static long byteArray4ToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset + 0] & 0xffL) << 24) |
|
||||
((bytes[offset + 1] & 0xffL) << 16) |
|
||||
((bytes[offset + 2] & 0xffL) << 8) |
|
||||
((bytes[offset + 3] & 0xffL));
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset] & 0xffL) << 56) |
|
||||
((bytes[offset + 1] & 0xffL) << 48) |
|
||||
((bytes[offset + 2] & 0xffL) << 40) |
|
||||
((bytes[offset + 3] & 0xffL) << 32) |
|
||||
((bytes[offset + 4] & 0xffL) << 24) |
|
||||
((bytes[offset + 5] & 0xffL) << 16) |
|
||||
((bytes[offset + 6] & 0xffL) << 8) |
|
||||
((bytes[offset + 7] & 0xffL));
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import org.session.libsession.utilities.dynamiclanguage.LanguageString;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @deprecated Use a base activity that uses the {@link org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper}
|
||||
*/
|
||||
@Deprecated
|
||||
public class DynamicLanguage {
|
||||
|
||||
public void onCreate(Activity activity) {
|
||||
}
|
||||
|
||||
public void onResume(Activity activity) {
|
||||
}
|
||||
|
||||
public void updateServiceLocale(Service service) {
|
||||
setContextLocale(service, getSelectedLocale(service));
|
||||
}
|
||||
|
||||
public Locale getCurrentLocale() {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
static int getLayoutDirection(Context context) {
|
||||
Configuration configuration = context.getResources().getConfiguration();
|
||||
return configuration.getLayoutDirection();
|
||||
}
|
||||
|
||||
private static void setContextLocale(Context context, Locale selectedLocale) {
|
||||
Configuration configuration = context.getResources().getConfiguration();
|
||||
|
||||
if (!configuration.locale.equals(selectedLocale)) {
|
||||
configuration.setLocale(selectedLocale);
|
||||
context.getResources().updateConfiguration(configuration,
|
||||
context.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
|
||||
private static Locale getSelectedLocale(Context context) {
|
||||
Locale locale = LanguageString.parseLocale(TextSecurePreferences.getLanguage(context));
|
||||
if (locale == null) {
|
||||
return Locale.getDefault();
|
||||
} else {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public interface FutureTaskListener<V> {
|
||||
public void onSuccess(V result);
|
||||
public void onFailure(ExecutionException exception);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
public class LinkedBlockingLifoQueue<E> extends LinkedBlockingDeque<E> {
|
||||
@Override
|
||||
public void put(E runnable) throws InterruptedException {
|
||||
super.putFirst(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E runnable) {
|
||||
super.addFirst(runnable);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(E runnable) {
|
||||
super.addFirst(runnable);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
public class ListenableFutureTask<V> extends FutureTask<V> {
|
||||
|
||||
private final List<FutureTaskListener<V>> listeners = new LinkedList<>();
|
||||
|
||||
@Nullable
|
||||
private final Object identifier;
|
||||
|
||||
@Nullable
|
||||
private final Executor callbackExecutor;
|
||||
|
||||
public ListenableFutureTask(Callable<V> callable) {
|
||||
this(callable, null);
|
||||
}
|
||||
|
||||
public ListenableFutureTask(Callable<V> callable, @Nullable Object identifier) {
|
||||
this(callable, identifier, null);
|
||||
}
|
||||
|
||||
public ListenableFutureTask(Callable<V> callable, @Nullable Object identifier, @Nullable Executor callbackExecutor) {
|
||||
super(callable);
|
||||
this.identifier = identifier;
|
||||
this.callbackExecutor = callbackExecutor;
|
||||
}
|
||||
|
||||
|
||||
public ListenableFutureTask(final V result) {
|
||||
this(result, null);
|
||||
}
|
||||
|
||||
public ListenableFutureTask(final V result, @Nullable Object identifier) {
|
||||
super(new Callable<V>() {
|
||||
@Override
|
||||
public V call() throws Exception {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
this.identifier = identifier;
|
||||
this.callbackExecutor = null;
|
||||
this.run();
|
||||
}
|
||||
|
||||
public synchronized void addListener(FutureTaskListener<V> listener) {
|
||||
if (this.isDone()) {
|
||||
callback(listener);
|
||||
} else {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removeListener(FutureTaskListener<V> listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void done() {
|
||||
callback();
|
||||
}
|
||||
|
||||
private void callback() {
|
||||
Runnable callbackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (FutureTaskListener<V> listener : listeners) {
|
||||
callback(listener);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (callbackExecutor == null) callbackRunnable.run();
|
||||
else callbackExecutor.execute(callbackRunnable);
|
||||
}
|
||||
|
||||
private void callback(FutureTaskListener<V> listener) {
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onSuccess(get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other != null && other instanceof ListenableFutureTask && this.identifier != null) {
|
||||
return identifier.equals(other);
|
||||
} else {
|
||||
return super.equals(other);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (identifier != null) return identifier.hashCode();
|
||||
else return super.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
|
||||
data class ProfilePictureModifiedEvent(val recipient: Recipient)
|
@ -0,0 +1,46 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
|
||||
class SSKEnvironment(
|
||||
val typingIndicators: TypingIndicatorsProtocol,
|
||||
val blockManager: BlockingManagerProtocol,
|
||||
val readReceiptManager: ReadReceiptManagerProtocol,
|
||||
val profileManager: ProfileManagerProtocol
|
||||
) {
|
||||
interface TypingIndicatorsProtocol {
|
||||
fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int)
|
||||
fun didReceiveTypingStoppedMessage(context: Context, threadId: Long, author: Address, device: Int, isReplacedByIncomingMessage: Boolean)
|
||||
fun didReceiveIncomingMessage(context: Context, threadId: Long, author: Address, device: Int)
|
||||
}
|
||||
|
||||
interface BlockingManagerProtocol {
|
||||
fun isRecipientIdBlocked(publicKey: String): Boolean
|
||||
}
|
||||
|
||||
interface ReadReceiptManagerProtocol {
|
||||
fun processReadReceipts(fromRecipientId: String, sentTimestamps: List<Long>, readTimestamp: Long)
|
||||
}
|
||||
|
||||
interface ProfileManagerProtocol {
|
||||
fun setDisplayName(recipient: Recipient, displayName: String)
|
||||
fun setProfilePictureURL(recipient: Recipient, profilePictureURL: String)
|
||||
fun setProfileKey(recipient: Recipient, profileKey: ByteArray)
|
||||
fun setUnidentifiedAccessMode(recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
||||
fun updateOpenGroupProfilePicturesIfNeeded()
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var shared: SSKEnvironment
|
||||
|
||||
fun configure(typingIndicators: TypingIndicatorsProtocol,
|
||||
blockManager: BlockingManagerProtocol,
|
||||
readReceiptManager: ReadReceiptManagerProtocol,
|
||||
profileManager: ProfileManagerProtocol) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = SSKEnvironment(typingIndicators, blockManager, readReceiptManager, profileManager)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* A <code><em>Soft</em>HashMap</code> is a memory-constrained map that stores its <em>values</em> in
|
||||
* {@link SoftReference SoftReference}s. (Contrast this with the JDK's
|
||||
* {@link WeakHashMap WeakHashMap}, which uses weak references for its <em>keys</em>, which is of little value if you
|
||||
* want the cache to auto-resize itself based on memory constraints).
|
||||
* <p/>
|
||||
* Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory
|
||||
* limitations and garbage collection. This ensures that the cache will not cause memory leaks by holding strong
|
||||
* references to all of its values.
|
||||
* <p/>
|
||||
* This class is a generics-enabled Map based on initial ideas from Heinz Kabutz's and Sydney Redelinghuys's
|
||||
* <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version (with their approval)</a>, with
|
||||
* continued modifications.
|
||||
* <p/>
|
||||
* This implementation is thread-safe and usable in concurrent environments.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public class SoftHashMap<K, V> implements Map<K, V> {
|
||||
|
||||
/**
|
||||
* The default value of the RETENTION_SIZE attribute, equal to 100.
|
||||
*/
|
||||
private static final int DEFAULT_RETENTION_SIZE = 100;
|
||||
|
||||
/**
|
||||
* The internal HashMap that will hold the SoftReference.
|
||||
*/
|
||||
private final Map<K, SoftValue<V, K>> map;
|
||||
|
||||
/**
|
||||
* The number of strong references to hold internally, that is, the number of instances to prevent
|
||||
* from being garbage collected automatically (unlike other soft references).
|
||||
*/
|
||||
private final int RETENTION_SIZE;
|
||||
|
||||
/**
|
||||
* The FIFO list of strong references (not to be garbage collected), order of last access.
|
||||
*/
|
||||
private final Queue<V> strongReferences; //guarded by 'strongReferencesLock'
|
||||
private final ReentrantLock strongReferencesLock;
|
||||
|
||||
/**
|
||||
* Reference queue for cleared SoftReference objects.
|
||||
*/
|
||||
private final ReferenceQueue<? super V> queue;
|
||||
|
||||
/**
|
||||
* Creates a new SoftHashMap with a default retention size size of
|
||||
* {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
|
||||
*
|
||||
* @see #SoftHashMap(int)
|
||||
*/
|
||||
public SoftHashMap() {
|
||||
this(DEFAULT_RETENTION_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SoftHashMap with the specified retention size.
|
||||
* <p/>
|
||||
* The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
|
||||
* (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to
|
||||
* allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
|
||||
* elements retained after a GC due to the strong references.
|
||||
* <p/>
|
||||
* Note that in a highly concurrent environments the exact total number of strong references may differ slightly
|
||||
* than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low
|
||||
* water mark.
|
||||
*
|
||||
* @param retentionSize the total number of most recent entries in the map that will be strongly referenced
|
||||
* (retained), preventing them from being eagerly garbage collected by the JVM.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public SoftHashMap(int retentionSize) {
|
||||
super();
|
||||
RETENTION_SIZE = Math.max(0, retentionSize);
|
||||
queue = new ReferenceQueue<V>();
|
||||
strongReferencesLock = new ReentrantLock();
|
||||
map = new ConcurrentHashMap<K, SoftValue<V, K>>();
|
||||
strongReferences = new ConcurrentLinkedQueue<V>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SoftHashMap} backed by the specified {@code source}, with a default retention
|
||||
* size of {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
|
||||
*
|
||||
* @param source the backing map to populate this {@code SoftHashMap}
|
||||
* @see #SoftHashMap(Map,int)
|
||||
*/
|
||||
public SoftHashMap(Map<K, V> source) {
|
||||
this(DEFAULT_RETENTION_SIZE);
|
||||
putAll(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SoftHashMap} backed by the specified {@code source}, with the specified retention size.
|
||||
* <p/>
|
||||
* The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
|
||||
* (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to
|
||||
* allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
|
||||
* elements retained after a GC due to the strong references.
|
||||
* <p/>
|
||||
* Note that in a highly concurrent environments the exact total number of strong references may differ slightly
|
||||
* than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low
|
||||
* water mark.
|
||||
*
|
||||
* @param source the backing map to populate this {@code SoftHashMap}
|
||||
* @param retentionSize the total number of most recent entries in the map that will be strongly referenced
|
||||
* (retained), preventing them from being eagerly garbage collected by the JVM.
|
||||
*/
|
||||
public SoftHashMap(Map<K, V> source, int retentionSize) {
|
||||
this(retentionSize);
|
||||
putAll(source);
|
||||
}
|
||||
|
||||
public V get(Object key) {
|
||||
processQueue();
|
||||
|
||||
V result = null;
|
||||
SoftValue<V, K> value = map.get(key);
|
||||
|
||||
if (value != null) {
|
||||
//unwrap the 'real' value from the SoftReference
|
||||
result = value.get();
|
||||
if (result == null) {
|
||||
//The wrapped value was garbage collected, so remove this entry from the backing map:
|
||||
//noinspection SuspiciousMethodCalls
|
||||
map.remove(key);
|
||||
} else {
|
||||
//Add this value to the beginning of the strong reference queue (FIFO).
|
||||
addToStrongReferences(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addToStrongReferences(V result) {
|
||||
strongReferencesLock.lock();
|
||||
try {
|
||||
strongReferences.add(result);
|
||||
trimStrongReferencesIfNecessary();
|
||||
} finally {
|
||||
strongReferencesLock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Guarded by the strongReferencesLock in the addToStrongReferences method
|
||||
|
||||
private void trimStrongReferencesIfNecessary() {
|
||||
//trim the strong ref queue if necessary:
|
||||
while (strongReferences.size() > RETENTION_SIZE) {
|
||||
strongReferences.poll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map
|
||||
* by looking them up using the SoftValue.key data member.
|
||||
*/
|
||||
private void processQueue() {
|
||||
SoftValue sv;
|
||||
while ((sv = (SoftValue) queue.poll()) != null) {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
map.remove(sv.key); // we can access private data!
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
processQueue();
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
processQueue();
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object value) {
|
||||
processQueue();
|
||||
Collection values = values();
|
||||
return values != null && values.contains(value);
|
||||
}
|
||||
|
||||
public void putAll(@NonNull Map<? extends K, ? extends V> m) {
|
||||
if (m == null || m.isEmpty()) {
|
||||
processQueue();
|
||||
return;
|
||||
}
|
||||
for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull Set<K> keySet() {
|
||||
processQueue();
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
public @NonNull Collection<V> values() {
|
||||
processQueue();
|
||||
Collection<K> keys = map.keySet();
|
||||
if (keys.isEmpty()) {
|
||||
//noinspection unchecked
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
Collection<V> values = new ArrayList<V>(keys.size());
|
||||
for (K key : keys) {
|
||||
V v = get(key);
|
||||
if (v != null) {
|
||||
values.add(v);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection.
|
||||
*/
|
||||
public V put(@NonNull K key, @NonNull V value) {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
|
||||
SoftValue<V, K> previous = map.put(key, sv);
|
||||
addToStrongReferences(value);
|
||||
return previous != null ? previous.get() : null;
|
||||
}
|
||||
|
||||
public V remove(Object key) {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
SoftValue<V, K> raw = map.remove(key);
|
||||
return raw != null ? raw.get() : null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
strongReferencesLock.lock();
|
||||
try {
|
||||
strongReferences.clear();
|
||||
} finally {
|
||||
strongReferencesLock.unlock();
|
||||
}
|
||||
processQueue(); // throw out garbage collected values
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public @NonNull Set<Entry<K, V>> entrySet() {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
Collection<K> keys = map.keySet();
|
||||
if (keys.isEmpty()) {
|
||||
//noinspection unchecked
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
Map<K, V> kvPairs = new HashMap<K, V>(keys.size());
|
||||
for (K key : keys) {
|
||||
V v = get(key);
|
||||
if (v != null) {
|
||||
kvPairs.put(key, v);
|
||||
}
|
||||
}
|
||||
return kvPairs.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* We define our own subclass of SoftReference which contains
|
||||
* not only the value but also the key to make it easier to find
|
||||
* the entry in the HashMap after it's been garbage collected.
|
||||
*/
|
||||
private static class SoftValue<V, K> extends SoftReference<V> {
|
||||
|
||||
private final K key;
|
||||
|
||||
/**
|
||||
* Constructs a new instance, wrapping the value, key, and queue, as
|
||||
* required by the superclass.
|
||||
*
|
||||
* @param value the map value
|
||||
* @param key the map key
|
||||
* @param queue the soft reference queue to poll to determine if the entry had been reaped by the GC.
|
||||
*/
|
||||
private SoftValue(V value, K key, ReferenceQueue<? super V> queue) {
|
||||
super(value, queue);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -386,6 +386,7 @@ object TextSecurePreferences {
|
||||
setIntegerPrefrence(context, PROFILE_AVATAR_ID_PREF, id)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getProfileAvatarId(context: Context): Int {
|
||||
return getIntegerPreference(context, PROFILE_AVATAR_ID_PREF, 0)
|
||||
}
|
||||
@ -606,6 +607,7 @@ object TextSecurePreferences {
|
||||
return getStringPreference(context, UPDATE_APK_DIGEST, null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLocalNumber(context: Context): String? {
|
||||
return getStringPreference(context, LOCAL_NUMBER_PREF, null)
|
||||
}
|
||||
@ -820,6 +822,7 @@ object TextSecurePreferences {
|
||||
setIntegerPrefrence(context, PASSPHRASE_TIMEOUT_INTERVAL_PREF, interval)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLanguage(context: Context): String? {
|
||||
return getStringPreference(context, LANGUAGE_PREF, "zz")
|
||||
}
|
||||
@ -1100,6 +1103,7 @@ object TextSecurePreferences {
|
||||
setBooleanPreference(context, "is_chat_set_up?chat=$id", true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getMasterHexEncodedPublicKey(context: Context): String? {
|
||||
return getStringPreference(context, "master_hex_encoded_public_key", null)
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
|
||||
import org.session.libsignal.libsignal.logging.Log;
|
||||
|
||||
import org.session.libsession.R;
|
||||
|
||||
public class ThemeUtil {
|
||||
private static final String TAG = ThemeUtil.class.getSimpleName();
|
||||
|
||||
public static boolean isDarkTheme(@NonNull Context context) {
|
||||
return getAttributeText(context, R.attr.theme_type, "light").equals("dark");
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int getThemedColor(@NonNull Context context, @AttrRes int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
|
||||
if (theme.resolveAttribute(attr, typedValue, true)) {
|
||||
return typedValue.data;
|
||||
} else {
|
||||
Log.e(TAG, "Couldn't find a color attribute with id: " + attr);
|
||||
return Color.RED;
|
||||
}
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
public static int getThemedDrawableResId(@NonNull Context context, @AttrRes int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
|
||||
if (theme.resolveAttribute(attr, typedValue, true)) {
|
||||
return typedValue.resourceId;
|
||||
} else {
|
||||
Log.e(TAG, "Couldn't find a drawable attribute with id: " + attr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static LayoutInflater getThemedInflater(@NonNull Context context, @NonNull LayoutInflater inflater, @StyleRes int theme) {
|
||||
Context contextThemeWrapper = new ContextThemeWrapper(context, theme);
|
||||
return inflater.cloneInContext(contextThemeWrapper);
|
||||
}
|
||||
|
||||
private static String getAttributeText(Context context, int attribute, String defaultValue) {
|
||||
TypedValue outValue = new TypedValue();
|
||||
|
||||
if (context.getTheme().resolveAttribute(attribute, outValue, true)) {
|
||||
CharSequence charSequence = outValue.coerceToString();
|
||||
if (charSequence != null) {
|
||||
return charSequence.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
@ -1,6 +1,61 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object Util {
|
||||
@Volatile
|
||||
private var handler: Handler? = null
|
||||
|
||||
fun isMainThread(): Boolean {
|
||||
return Looper.myLooper() == Looper.getMainLooper()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun uri(uri: String?): Uri? {
|
||||
return if (uri == null) null else Uri.parse(uri)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runOnMain(runnable: Runnable) {
|
||||
if (isMainThread()) runnable.run()
|
||||
else getHandler()?.post(runnable)
|
||||
}
|
||||
|
||||
private fun getHandler(): Handler? {
|
||||
if (handler == null) {
|
||||
synchronized(Util::class.java) {
|
||||
if (handler == null) {
|
||||
handler = Handler(Looper.getMainLooper())
|
||||
}
|
||||
}
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun wait(lock: Object, timeout: Long) {
|
||||
try {
|
||||
lock.wait(timeout)
|
||||
} catch (ie: InterruptedException) {
|
||||
throw AssertionError(ie)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun newSingleThreadedLifoExecutor(): ExecutorService {
|
||||
val executor = ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, LinkedBlockingLifoQueue<Runnable>())
|
||||
executor.execute {
|
||||
Thread.currentThread().priority = Thread.MIN_PRIORITY
|
||||
}
|
||||
return executor
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun join(list: Collection<String?>, delimiter: String?): String {
|
||||
val result = StringBuilder()
|
||||
var i = 0
|
||||
@ -10,4 +65,10 @@ object Util {
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun equals(a: Any?, b: Any?): Boolean {
|
||||
return a === b || a != null && a == b
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import org.session.libsession.utilities.concurrent.ListenableFuture;
|
||||
import org.session.libsession.utilities.concurrent.SettableFuture;
|
||||
import org.session.libsession.utilities.views.Stub;
|
||||
|
||||
public class ViewUtil {
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setBackground(final @NonNull View v, final @Nullable Drawable drawable) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||
v.setBackground(drawable);
|
||||
} else {
|
||||
v.setBackgroundDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setY(final @NonNull View v, final int y) {
|
||||
if (VERSION.SDK_INT >= 11) {
|
||||
ViewCompat.setY(v, y);
|
||||
} else {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
|
||||
params.topMargin = y;
|
||||
v.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getY(final @NonNull View v) {
|
||||
if (VERSION.SDK_INT >= 11) {
|
||||
return ViewCompat.getY(v);
|
||||
} else {
|
||||
return ((ViewGroup.MarginLayoutParams)v.getLayoutParams()).topMargin;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setX(final @NonNull View v, final int x) {
|
||||
if (VERSION.SDK_INT >= 11) {
|
||||
ViewCompat.setX(v, x);
|
||||
} else {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
|
||||
params.leftMargin = x;
|
||||
v.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getX(final @NonNull View v) {
|
||||
if (VERSION.SDK_INT >= 11) {
|
||||
return ViewCompat.getX(v);
|
||||
} else {
|
||||
return ((LayoutParams)v.getLayoutParams()).leftMargin;
|
||||
}
|
||||
}
|
||||
|
||||
public static void swapChildInPlace(ViewGroup parent, View toRemove, View toAdd, int defaultIndex) {
|
||||
int childIndex = parent.indexOfChild(toRemove);
|
||||
if (childIndex > -1) parent.removeView(toRemove);
|
||||
parent.addView(toAdd, childIndex > -1 ? childIndex : defaultIndex);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends View> T inflateStub(@NonNull View parent, @IdRes int stubId) {
|
||||
return (T)((ViewStub)parent.findViewById(stubId)).inflate();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends View> T findById(@NonNull View parent, @IdRes int resId) {
|
||||
return (T) parent.findViewById(resId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends View> T findById(@NonNull Activity parent, @IdRes int resId) {
|
||||
return (T) parent.findViewById(resId);
|
||||
}
|
||||
|
||||
public static <T extends View> Stub<T> findStubById(@NonNull Activity parent, @IdRes int resId) {
|
||||
return new Stub<T>((ViewStub)parent.findViewById(resId));
|
||||
}
|
||||
|
||||
private static Animation getAlphaAnimation(float from, float to, int duration) {
|
||||
final Animation anim = new AlphaAnimation(from, to);
|
||||
anim.setInterpolator(new FastOutSlowInInterpolator());
|
||||
anim.setDuration(duration);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static void fadeIn(final @NonNull View view, final int duration) {
|
||||
animateIn(view, getAlphaAnimation(0f, 1f, duration));
|
||||
}
|
||||
|
||||
public static ListenableFuture<Boolean> fadeOut(final @NonNull View view, final int duration) {
|
||||
return fadeOut(view, duration, View.GONE);
|
||||
}
|
||||
|
||||
public static ListenableFuture<Boolean> fadeOut(@NonNull View view, int duration, int visibility) {
|
||||
return animateOut(view, getAlphaAnimation(1f, 0f, duration), visibility);
|
||||
}
|
||||
|
||||
public static ListenableFuture<Boolean> animateOut(final @NonNull View view, final @NonNull Animation animation, final int visibility) {
|
||||
final SettableFuture future = new SettableFuture();
|
||||
if (view.getVisibility() == visibility) {
|
||||
future.set(true);
|
||||
} else {
|
||||
view.clearAnimation();
|
||||
animation.reset();
|
||||
animation.setStartTime(0);
|
||||
animation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
view.setVisibility(visibility);
|
||||
future.set(true);
|
||||
}
|
||||
});
|
||||
view.startAnimation(animation);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
public static void animateIn(final @NonNull View view, final @NonNull Animation animation) {
|
||||
if (view.getVisibility() == View.VISIBLE) return;
|
||||
|
||||
view.clearAnimation();
|
||||
animation.reset();
|
||||
animation.setStartTime(0);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.startAnimation(animation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends View> T inflate(@NonNull LayoutInflater inflater,
|
||||
@NonNull ViewGroup parent,
|
||||
@LayoutRes int layoutResId)
|
||||
{
|
||||
return (T)(inflater.inflate(layoutResId, parent, false));
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
public static void setTextViewGravityStart(final @NonNull TextView textView, @NonNull Context context) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
if (DynamicLanguage.getLayoutDirection(context) == View.LAYOUT_DIRECTION_RTL) {
|
||||
textView.setGravity(Gravity.RIGHT);
|
||||
} else {
|
||||
textView.setGravity(Gravity.LEFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void mirrorIfRtl(View view, Context context) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1 &&
|
||||
DynamicLanguage.getLayoutDirection(context) == View.LAYOUT_DIRECTION_RTL) {
|
||||
view.setScaleX(-1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public static int dpToPx(Context context, int dp) {
|
||||
return (int)((dp * context.getResources().getDisplayMetrics().density) + 0.5);
|
||||
}
|
||||
|
||||
public static void updateLayoutParams(@NonNull View view, int width, int height) {
|
||||
view.getLayoutParams().width = width;
|
||||
view.getLayoutParams().height = height;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static int getLeftMargin(@NonNull View view) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
|
||||
}
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
|
||||
}
|
||||
|
||||
public static int getRightMargin(@NonNull View view) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
|
||||
}
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
|
||||
}
|
||||
|
||||
public static void setLeftMargin(@NonNull View view, int margin) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin = margin;
|
||||
} else {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin = margin;
|
||||
}
|
||||
view.forceLayout();
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static void setTopMargin(@NonNull View view, int margin) {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin = margin;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static void setPaddingTop(@NonNull View view, int padding) {
|
||||
view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom());
|
||||
}
|
||||
|
||||
public static void setPaddingBottom(@NonNull View view, int padding) {
|
||||
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), padding);
|
||||
}
|
||||
|
||||
public static boolean isPointInsideView(@NonNull View view, float x, float y) {
|
||||
int[] location = new int[2];
|
||||
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
int viewX = location[0];
|
||||
int viewY = location[1];
|
||||
|
||||
return x > viewX && x < viewX + view.getWidth() &&
|
||||
y > viewY && y < viewY + view.getHeight();
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package org.session.libsession.utilities.color;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.session.libsession.utilities.ThemeUtil.isDarkTheme;
|
||||
|
||||
public enum MaterialColor {
|
||||
CRIMSON (R.color.conversation_crimson, R.color.conversation_crimson_tint, R.color.conversation_crimson_shade, "red"),
|
||||
VERMILLION (R.color.conversation_vermillion, R.color.conversation_vermillion_tint, R.color.conversation_vermillion_shade, "orange"),
|
||||
BURLAP (R.color.conversation_burlap, R.color.conversation_burlap_tint, R.color.conversation_burlap_shade, "brown"),
|
||||
FOREST (R.color.conversation_forest, R.color.conversation_forest_tint, R.color.conversation_forest_shade, "green"),
|
||||
WINTERGREEN(R.color.conversation_wintergreen, R.color.conversation_wintergreen_tint, R.color.conversation_wintergreen_shade, "light_green"),
|
||||
TEAL (R.color.conversation_teal, R.color.conversation_teal_tint, R.color.conversation_teal_shade, "teal"),
|
||||
BLUE (R.color.conversation_blue, R.color.conversation_blue_tint, R.color.conversation_blue_shade, "blue"),
|
||||
INDIGO (R.color.conversation_indigo, R.color.conversation_indigo_tint, R.color.conversation_indigo_shade, "indigo"),
|
||||
VIOLET (R.color.conversation_violet, R.color.conversation_violet_tint, R.color.conversation_violet_shade, "purple"),
|
||||
PLUM (R.color.conversation_plumb, R.color.conversation_plumb_tint, R.color.conversation_plumb_shade, "pink"),
|
||||
TAUPE (R.color.conversation_taupe, R.color.conversation_taupe_tint, R.color.conversation_taupe_shade, "blue_grey"),
|
||||
STEEL (R.color.conversation_steel, R.color.conversation_steel_tint, R.color.conversation_steel_shade, "grey"),
|
||||
GROUP (R.color.conversation_group, R.color.conversation_group_tint, R.color.conversation_group_shade, "blue");
|
||||
|
||||
private static final Map<String, MaterialColor> COLOR_MATCHES = new HashMap<String, MaterialColor>() {{
|
||||
put("red", CRIMSON);
|
||||
put("deep_orange", CRIMSON);
|
||||
put("orange", VERMILLION);
|
||||
put("amber", VERMILLION);
|
||||
put("brown", BURLAP);
|
||||
put("yellow", BURLAP);
|
||||
put("pink", PLUM);
|
||||
put("purple", VIOLET);
|
||||
put("deep_purple", VIOLET);
|
||||
put("indigo", INDIGO);
|
||||
put("blue", BLUE);
|
||||
put("light_blue", BLUE);
|
||||
put("cyan", TEAL);
|
||||
put("teal", TEAL);
|
||||
put("green", FOREST);
|
||||
put("light_green", WINTERGREEN);
|
||||
put("lime", WINTERGREEN);
|
||||
put("blue_grey", TAUPE);
|
||||
put("grey", STEEL);
|
||||
put("group_color", GROUP);
|
||||
}};
|
||||
|
||||
private final @ColorRes int mainColor;
|
||||
private final @ColorRes int tintColor;
|
||||
private final @ColorRes int shadeColor;
|
||||
|
||||
private final String serialized;
|
||||
|
||||
|
||||
MaterialColor(@ColorRes int mainColor, @ColorRes int tintColor, @ColorRes int shadeColor, String serialized) {
|
||||
this.mainColor = mainColor;
|
||||
this.tintColor = tintColor;
|
||||
this.shadeColor = shadeColor;
|
||||
this.serialized = serialized;
|
||||
}
|
||||
|
||||
public @ColorInt int toConversationColor(@NonNull Context context) {
|
||||
return context.getResources().getColor(mainColor);
|
||||
}
|
||||
|
||||
public @ColorInt int toAvatarColor(@NonNull Context context) {
|
||||
return context.getResources().getColor(isDarkTheme(context) ? shadeColor : mainColor);
|
||||
}
|
||||
|
||||
public @ColorInt int toActionBarColor(@NonNull Context context) {
|
||||
return context.getResources().getColor(mainColor);
|
||||
}
|
||||
|
||||
public @ColorInt int toStatusBarColor(@NonNull Context context) {
|
||||
return context.getResources().getColor(shadeColor);
|
||||
}
|
||||
|
||||
public @ColorRes int toQuoteBarColorResource(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
return isDarkTheme(context) ? tintColor : shadeColor ;
|
||||
}
|
||||
return R.color.core_white;
|
||||
}
|
||||
|
||||
public @ColorInt int toQuoteBackgroundColor(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
int color = toConversationColor(context);
|
||||
int alpha = isDarkTheme(context) ? (int) (0.2 * 255) : (int) (0.4 * 255);
|
||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
return context.getResources().getColor(isDarkTheme(context) ? R.color.transparent_black_70
|
||||
: R.color.transparent_white_aa);
|
||||
}
|
||||
|
||||
public @ColorInt int toQuoteFooterColor(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
int color = toConversationColor(context);
|
||||
int alpha = isDarkTheme(context) ? (int) (0.4 * 255) : (int) (0.6 * 255);
|
||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
return context.getResources().getColor(isDarkTheme(context) ? R.color.transparent_black_90
|
||||
: R.color.transparent_white_bb);
|
||||
}
|
||||
|
||||
public boolean represents(Context context, int colorValue) {
|
||||
return context.getResources().getColor(mainColor) == colorValue ||
|
||||
context.getResources().getColor(tintColor) == colorValue ||
|
||||
context.getResources().getColor(shadeColor) == colorValue;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
public static MaterialColor fromSerialized(String serialized) throws UnknownColorException {
|
||||
if (COLOR_MATCHES.containsKey(serialized)) {
|
||||
return COLOR_MATCHES.get(serialized);
|
||||
}
|
||||
|
||||
throw new UnknownColorException("Unknown color: " + serialized);
|
||||
}
|
||||
|
||||
public static class UnknownColorException extends Exception {
|
||||
public UnknownColorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package org.session.libsession.utilities.color;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MaterialColors {
|
||||
|
||||
public static final MaterialColorList CONVERSATION_PALETTE = new MaterialColorList(new ArrayList<>(Arrays.asList(
|
||||
MaterialColor.PLUM,
|
||||
MaterialColor.CRIMSON,
|
||||
MaterialColor.VERMILLION,
|
||||
MaterialColor.VIOLET,
|
||||
MaterialColor.BLUE,
|
||||
MaterialColor.INDIGO,
|
||||
MaterialColor.FOREST,
|
||||
MaterialColor.WINTERGREEN,
|
||||
MaterialColor.TEAL,
|
||||
MaterialColor.BURLAP,
|
||||
MaterialColor.TAUPE,
|
||||
MaterialColor.STEEL
|
||||
)));
|
||||
|
||||
public static class MaterialColorList {
|
||||
|
||||
private final List<MaterialColor> colors;
|
||||
|
||||
private MaterialColorList(List<MaterialColor> colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public MaterialColor get(int index) {
|
||||
return colors.get(index);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return colors.size();
|
||||
}
|
||||
|
||||
public @Nullable MaterialColor getByColor(Context context, int colorValue) {
|
||||
for (MaterialColor color : colors) {
|
||||
if (color.represents(context, colorValue)) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int[] asConversationColorArray(@NonNull Context context) {
|
||||
int[] results = new int[colors.size()];
|
||||
int index = 0;
|
||||
|
||||
for (MaterialColor color : colors) {
|
||||
results[index++] = color.toConversationColor(context);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package org.session.libsession.utilities.concurrent;
|
||||
|
||||
import org.session.libsession.utilities.concurrent.ListenableFuture.Listener;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class AssertedSuccessListener<T> implements Listener<T> {
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package org.session.libsession.utilities.concurrent;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public interface ListenableFuture<T> extends Future<T> {
|
||||
void addListener(Listener<T> listener);
|
||||
|
||||
public interface Listener<T> {
|
||||
public void onSuccess(T result);
|
||||
public void onFailure(ExecutionException e);
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package org.session.libsession.utilities.concurrent;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SettableFuture<T> implements ListenableFuture<T> {
|
||||
|
||||
private final List<Listener<T>> listeners = new LinkedList<>();
|
||||
|
||||
private boolean completed;
|
||||
private boolean canceled;
|
||||
private volatile T result;
|
||||
private volatile Throwable exception;
|
||||
|
||||
public SettableFuture() { }
|
||||
|
||||
public SettableFuture(T value) {
|
||||
this.result = value;
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (!completed && !canceled) {
|
||||
canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isCancelled() {
|
||||
return canceled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isDone() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
public boolean set(T result) {
|
||||
synchronized (this) {
|
||||
if (completed || canceled) return false;
|
||||
|
||||
this.result = result;
|
||||
this.completed = true;
|
||||
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
notifyAllListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setException(Throwable throwable) {
|
||||
synchronized (this) {
|
||||
if (completed || canceled) return false;
|
||||
|
||||
this.exception = throwable;
|
||||
this.completed = true;
|
||||
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
notifyAllListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void deferTo(ListenableFuture<T> other) {
|
||||
other.addListener(new Listener<T>() {
|
||||
@Override
|
||||
public void onSuccess(T result) {
|
||||
SettableFuture.this.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
SettableFuture.this.setException(e.getCause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get() throws InterruptedException, ExecutionException {
|
||||
while (!completed) wait();
|
||||
|
||||
if (exception != null) throw new ExecutionException(exception);
|
||||
else return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
while (!completed && System.currentTimeMillis() - startTime > unit.toMillis(timeout)) {
|
||||
wait(unit.toMillis(timeout));
|
||||
}
|
||||
|
||||
if (!completed) throw new TimeoutException();
|
||||
else return get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener<T> listener) {
|
||||
synchronized (this) {
|
||||
listeners.add(listener);
|
||||
|
||||
if (!completed) return;
|
||||
}
|
||||
|
||||
notifyListener(listener);
|
||||
}
|
||||
|
||||
private void notifyAllListeners() {
|
||||
List<Listener<T>> localListeners;
|
||||
|
||||
synchronized (this) {
|
||||
localListeners = new LinkedList<>(listeners);
|
||||
}
|
||||
|
||||
for (Listener<T> listener : localListeners) {
|
||||
notifyListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListener(Listener<T> listener) {
|
||||
if (exception != null) listener.onFailure(new ExecutionException(exception));
|
||||
else listener.onSuccess(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.session.libsession.utilities.concurrent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SignalExecutors {
|
||||
|
||||
public static final ExecutorService UNBOUNDED = Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded"));
|
||||
public static final ExecutorService BOUNDED = Executors.newFixedThreadPool(Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)), new NumberedThreadFactory("signal-bounded"));
|
||||
public static final ExecutorService SERIAL = Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial"));
|
||||
|
||||
public static ExecutorService newCachedSingleThreadExecutor(final String name) {
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name));
|
||||
executor.allowCoreThreadTimeOut(true);
|
||||
return executor;
|
||||
}
|
||||
|
||||
private static class NumberedThreadFactory implements ThreadFactory {
|
||||
|
||||
private final String baseName;
|
||||
private final AtomicInteger counter;
|
||||
|
||||
NumberedThreadFactory(@NonNull String baseName) {
|
||||
this.baseName = baseName;
|
||||
this.counter = new AtomicInteger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
return new Thread(r, baseName + "-" + counter.getAndIncrement());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package org.session.libsession.utilities.concurrent;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
public class SimpleTask {
|
||||
|
||||
/**
|
||||
* Runs a task in the background and passes the result of the computation to a task that is run
|
||||
* on the main thread. Will only invoke the {@code foregroundTask} if the provided {@link Lifecycle}
|
||||
* is in a valid (i.e. visible) state at that time. In this way, it is very similar to
|
||||
* {@link AsyncTask}, but is safe in that you can guarantee your task won't be called when your
|
||||
* view is in an invalid state.
|
||||
*/
|
||||
public static <E> void run(@NonNull Lifecycle lifecycle, @NonNull BackgroundTask<E> backgroundTask, @NonNull ForegroundTask<E> foregroundTask) {
|
||||
if (!isValid(lifecycle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
final E result = backgroundTask.run();
|
||||
|
||||
if (isValid(lifecycle)) {
|
||||
Util.runOnMain(() -> {
|
||||
if (isValid(lifecycle)) {
|
||||
foregroundTask.run(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task in the background and passes the result of the computation to a task that is run on
|
||||
* the main thread. Essentially {@link AsyncTask}, but lambda-compatible.
|
||||
*/
|
||||
public static <E> void run(@NonNull BackgroundTask<E> backgroundTask, @NonNull ForegroundTask<E> foregroundTask) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
final E result = backgroundTask.run();
|
||||
Util.runOnMain(() -> foregroundTask.run(result));
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isValid(@NonNull Lifecycle lifecycle) {
|
||||
return lifecycle.getCurrentState().isAtLeast(Lifecycle.State.CREATED);
|
||||
}
|
||||
|
||||
public interface BackgroundTask<E> {
|
||||
E run();
|
||||
}
|
||||
|
||||
public interface ForegroundTask<E> {
|
||||
void run(E result);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.session.libsession.utilities.dynamiclanguage;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.core.os.ConfigurationCompat;
|
||||
|
||||
import org.session.libsignal.libsignal.logging.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class DynamicLanguageActivityHelper {
|
||||
|
||||
private static final String TAG = DynamicLanguageActivityHelper.class.getSimpleName();
|
||||
|
||||
private static String reentryProtection;
|
||||
|
||||
/**
|
||||
* If the activity isn't in the specified language, it will restart the activity.
|
||||
*/
|
||||
@MainThread
|
||||
public static void recreateIfNotInCorrectLanguage(Activity activity, String language) {
|
||||
Locale currentActivityLocale = ConfigurationCompat.getLocales(activity.getResources().getConfiguration()).get(0);
|
||||
Locale selectedLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
||||
|
||||
if (currentActivityLocale.equals(selectedLocale)) {
|
||||
reentryProtection = "";
|
||||
return;
|
||||
}
|
||||
|
||||
String reentryKey = activity.getClass().getName() + ":" + selectedLocale;
|
||||
if (!reentryKey.equals(reentryProtection)) {
|
||||
reentryProtection = reentryKey;
|
||||
Log.d(TAG, String.format("Activity Locale %s, Selected locale %s, restarting", currentActivityLocale, selectedLocale));
|
||||
activity.recreate();
|
||||
} else {
|
||||
Log.d(TAG, String.format("Skipping recreate as looks like looping, Activity Locale %s, Selected locale %s", currentActivityLocale, selectedLocale));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.session.libsession.utilities.dynamiclanguage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Updates a context with an alternative language.
|
||||
*/
|
||||
public final class DynamicLanguageContextWrapper {
|
||||
|
||||
public static Context updateContext(Context context, String language) {
|
||||
final Locale newLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
||||
|
||||
Locale.setDefault(newLocale);
|
||||
|
||||
final Resources resources = context.getResources();
|
||||
final Configuration config = resources.getConfiguration();
|
||||
final Configuration newConfig = copyWithNewLocale(config, newLocale);
|
||||
|
||||
resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static Configuration copyWithNewLocale(Configuration config, Locale locale) {
|
||||
final Configuration copy = new Configuration(config);
|
||||
copy.setLocale(locale);
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.session.libsession.utilities.dynamiclanguage;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class LanguageString {
|
||||
|
||||
private LanguageString() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param languageString String in format language_REGION, e.g. en_US
|
||||
* @return Locale, or null if cannot parse
|
||||
*/
|
||||
@Nullable
|
||||
public static Locale parseLocale(@Nullable String languageString) {
|
||||
if (languageString == null || languageString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Locale locale = createLocale(languageString);
|
||||
|
||||
if (!isValid(locale)) {
|
||||
return null;
|
||||
} else {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
private static Locale createLocale(@NonNull String languageString) {
|
||||
final String language[] = languageString.split("_");
|
||||
if (language.length == 2) {
|
||||
return new Locale(language[0], language[1]);
|
||||
} else {
|
||||
return new Locale(language[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValid(@NonNull Locale locale) {
|
||||
try {
|
||||
return locale.getISO3Language() != null && locale.getISO3Country() != null;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.session.libsession.utilities.dynamiclanguage
|
||||
|
||||
import java.util.*
|
||||
|
||||
class LocaleParser(val helper: LocaleParserHelperProtocol) {
|
||||
companion object {
|
||||
lateinit var shared: LocaleParser
|
||||
|
||||
fun configure(helper: LocaleParserHelperProtocol) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = LocaleParser(helper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a language, gets the best choice from the apps list of supported languages and the
|
||||
* Systems set of languages.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun findBestMatchingLocaleForLanguage(language: String?): Locale? {
|
||||
val locale = LanguageString.parseLocale(language)
|
||||
return if (shared.helper.appSupportsTheExactLocale(locale)) {
|
||||
locale
|
||||
} else {
|
||||
shared.helper.findBestSystemLocale()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.session.libsession.utilities.dynamiclanguage
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
interface LocaleParserHelperProtocol {
|
||||
fun appSupportsTheExactLocale(locale: Locale?): Boolean
|
||||
fun findBestSystemLocale(): Locale
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.session.libsession.utilities.views;
|
||||
|
||||
|
||||
import android.view.ViewStub;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class Stub<T> {
|
||||
|
||||
private ViewStub viewStub;
|
||||
private T view;
|
||||
|
||||
public Stub(@NonNull ViewStub viewStub) {
|
||||
this.viewStub = viewStub;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (view == null) {
|
||||
view = (T)viewStub.inflate();
|
||||
viewStub = null;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public boolean resolved() {
|
||||
return view != null;
|
||||
}
|
||||
|
||||
}
|
11
libsession/src/main/res/drawable/avatar_gradient_dark.xml
Normal file
11
libsession/src/main/res/drawable/avatar_gradient_dark.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="@color/transparent_black_30"
|
||||
android:endColor="@color/transparent_black" />
|
||||
|
||||
</shape>
|
11
libsession/src/main/res/drawable/avatar_gradient_light.xml
Normal file
11
libsession/src/main/res/drawable/avatar_gradient_light.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="@color/transparent_white_30"
|
||||
android:endColor="@color/transparent" />
|
||||
|
||||
</shape>
|
4
libsession/src/main/res/drawable/ic_person_large.xml
Normal file
4
libsession/src/main/res/drawable/ic_person_large.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<vector android:height="260dp" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="260dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@color/gray20" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
311
libsession/src/main/res/values/arrays.xml
Normal file
311
libsession/src/main/res/values/arrays.xml
Normal file
@ -0,0 +1,311 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string-array name="language_entries">
|
||||
<item>@string/preferences__default</item>
|
||||
<item>English</item>
|
||||
<item>Arabic العربية</item>
|
||||
<item>Azərbaycan</item>
|
||||
<item>Bulgarian български</item>
|
||||
<item>Burmese ဗမာစကား</item>
|
||||
<item>Català</item>
|
||||
<item>Čeština</item>
|
||||
<item>Chinese (Simplified) 中文 (简体)</item>
|
||||
<item>Chinese (Traditional) 中文 (繁體)</item>
|
||||
<item>Cymraeg</item>
|
||||
<item>Dansk</item>
|
||||
<item>Deutsch</item>
|
||||
<item>Eesti</item>
|
||||
<item>Español</item>
|
||||
<item>Esperanto</item>
|
||||
<item>Euskara</item>
|
||||
<item>Français</item>
|
||||
<item>Gaeilge</item>
|
||||
<item>Galego</item>
|
||||
<item>Greek ελληνικά</item>
|
||||
<item>Hebrew עברית</item>
|
||||
<item>Hindi हिंदी</item>
|
||||
<item>Hrvatski</item>
|
||||
<item>Indonesia</item>
|
||||
<item>Italiano</item>
|
||||
<item>Japanese 日本語</item>
|
||||
<item>Khmer ភាសាខ្មែរ</item>
|
||||
<item>Kiswahili</item>
|
||||
<item>Korean 한국어</item>
|
||||
<item>Kurdí</item>
|
||||
<item>Lietuvių</item>
|
||||
<item>Luganda</item>
|
||||
<item>Magyar</item>
|
||||
<item>Macedonian македонски јазик</item>
|
||||
<item>Nederlands</item>
|
||||
<item>Norsk (bokmål)</item>
|
||||
<item>Norsk (nynorsk)</item>
|
||||
<item>Persian فارسی</item>
|
||||
<item>Polski</item>
|
||||
<item>Português</item>
|
||||
<item>Português do Brasil</item>
|
||||
<item>Quechua</item>
|
||||
<item>Română</item>
|
||||
<item>Russian Pусский</item>
|
||||
<item>Serbian српски</item>
|
||||
<item>Shqip</item>
|
||||
<item>Slovenščina</item>
|
||||
<item>Slovenský</item>
|
||||
<item>Suomi</item>
|
||||
<item>Svenska</item>
|
||||
<item>Telugu తెలుగు</item>
|
||||
<item>Thai ภาษาไทย</item>
|
||||
<item>Türkçe</item>
|
||||
<item>Ukrainian Українська</item>
|
||||
<item>Vietnamese Tiếng Việt</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="language_values">
|
||||
<item>zz</item>
|
||||
<item>en</item>
|
||||
<item>ar</item>
|
||||
<item>az</item>
|
||||
<item>bg</item>
|
||||
<item>my</item>
|
||||
<item>ca</item>
|
||||
<item>cs</item>
|
||||
<item>zh_CN</item>
|
||||
<item>zh_TW</item>
|
||||
<item>cy</item>
|
||||
<item>da</item>
|
||||
<item>de</item>
|
||||
<item>et</item>
|
||||
<item>es</item>
|
||||
<item>eo</item>
|
||||
<item>eu</item>
|
||||
<item>fr</item>
|
||||
<item>ga</item>
|
||||
<item>gl</item>
|
||||
<item>el</item>
|
||||
<item>iw</item>
|
||||
<item>hi</item>
|
||||
<item>hr</item>
|
||||
<item>in</item>
|
||||
<item>it</item>
|
||||
<item>ja</item>
|
||||
<item>km</item>
|
||||
<item>sw</item>
|
||||
<item>ko</item>
|
||||
<item>ku</item>
|
||||
<item>lt</item>
|
||||
<item>lg</item>
|
||||
<item>hu</item>
|
||||
<item>mk</item>
|
||||
<item>nl</item>
|
||||
<item>nb</item>
|
||||
<item>nn</item>
|
||||
<item>fa</item>
|
||||
<item>pl</item>
|
||||
<item>pt</item>
|
||||
<item>pt_BR</item>
|
||||
<item>qu_EC</item>
|
||||
<item>ro</item>
|
||||
<item>ru</item>
|
||||
<item>sr</item>
|
||||
<item>sq</item>
|
||||
<item>sl</item>
|
||||
<item>sk</item>
|
||||
<item>fi</item>
|
||||
<item>sv</item>
|
||||
<item>te</item>
|
||||
<item>th</item>
|
||||
<item>tr</item>
|
||||
<item>uk</item>
|
||||
<item>vi</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_theme_entries">
|
||||
<item>@string/preferences__light_theme</item>
|
||||
<item>@string/preferences__dark_theme</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_theme_values" translatable="false">
|
||||
<item>light</item>
|
||||
<item>dark</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_led_color_entries">
|
||||
<item>@string/preferences__green</item>
|
||||
<item>@string/preferences__red</item>
|
||||
<item>@string/preferences__blue</item>
|
||||
<item>@string/preferences__orange</item>
|
||||
<item>@string/preferences__cyan</item>
|
||||
<item>@string/preferences__magenta</item>
|
||||
<item>@string/preferences__white</item>
|
||||
<item>@string/preferences__none</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="pref_led_color_values" translatable="false">
|
||||
<item>green</item>
|
||||
<item>red</item>
|
||||
<item>blue</item>
|
||||
<item>yellow</item>
|
||||
<item>cyan</item>
|
||||
<item>magenta</item>
|
||||
<item>white</item>
|
||||
<item>none</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_led_blink_pattern_entries">
|
||||
<item>@string/preferences__fast</item>
|
||||
<item>@string/preferences__normal</item>
|
||||
<item>@string/preferences__slow</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_led_blink_pattern_values" translatable="false">
|
||||
<item>300,300</item>
|
||||
<item>500,2000</item>
|
||||
<item>3000,3000</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_repeat_alerts_entries">
|
||||
<item>@string/preferences__never</item>
|
||||
<item>@string/preferences__one_time</item>
|
||||
<item>@string/preferences__two_times</item>
|
||||
<item>@string/preferences__three_times</item>
|
||||
<item>@string/preferences__five_times</item>
|
||||
<item>@string/preferences__ten_times</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_repeat_alerts_values" translatable="false">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="default_or_custom_values" translatable="false">
|
||||
<item>default</item>
|
||||
<item>custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="default_or_custom_entries">
|
||||
<item>@string/arrays__use_default</item>
|
||||
<item>@string/arrays__use_custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mute_durations">
|
||||
<item>@string/arrays__mute_for_one_hour</item>
|
||||
<item>@string/arrays__mute_for_two_hours</item>
|
||||
<item>@string/arrays__mute_for_one_day</item>
|
||||
<item>@string/arrays__mute_for_seven_days</item>
|
||||
<item>@string/arrays__mute_for_one_year</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="recipient_vibrate_entries">
|
||||
<item>@string/arrays__settings_default</item>
|
||||
<item>@string/arrays__enabled</item>
|
||||
<item>@string/arrays__disabled</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="recipient_vibrate_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_notification_privacy_entries">
|
||||
<item>@string/arrays__name_and_message</item>
|
||||
<item>@string/arrays__name_only</item>
|
||||
<item>@string/arrays__no_name_or_message</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_notification_privacy_values">
|
||||
<item>all</item>
|
||||
<item>contact</item>
|
||||
<item>none</item>
|
||||
</string-array>
|
||||
|
||||
<!-- discrete MIME type (the part before the "/") -->
|
||||
<string-array name="pref_media_download_entries">
|
||||
<item>image</item>
|
||||
<item>audio</item>
|
||||
<item>video</item>
|
||||
<item>documents</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_media_download_values">
|
||||
<item>@string/arrays__images</item>
|
||||
<item>@string/arrays__audio</item>
|
||||
<item>@string/arrays__video</item>
|
||||
<item>@string/arrays__documents</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_media_download_mobile_data_default">
|
||||
<item>image</item>
|
||||
<item>audio</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_media_download_wifi_default">
|
||||
<item>image</item>
|
||||
<item>audio</item>
|
||||
<item>video</item>
|
||||
<item>documents</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_media_download_roaming_default" />
|
||||
|
||||
<!-- RedPhone -->
|
||||
|
||||
<integer-array name="expiration_times">
|
||||
<item>0</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>300</item>
|
||||
<item>1800</item>
|
||||
<item>3600</item>
|
||||
<item>21600</item>
|
||||
<item>43200</item>
|
||||
<item>86400</item>
|
||||
<item>604800</item>
|
||||
</integer-array>
|
||||
|
||||
<array name="scribble_colors">
|
||||
<item>#ffffff</item>
|
||||
<item>#ff0000</item>
|
||||
<item>#ff00ff</item>
|
||||
<item>#0000ff</item>
|
||||
<item>#00ffff</item>
|
||||
<item>#00ff00</item>
|
||||
<item>#ffff00</item>
|
||||
<item>#ff5500</item>
|
||||
<item>#000000</item>
|
||||
</array>
|
||||
|
||||
<string-array name="pref_message_font_size_entries">
|
||||
<item>@string/arrays__small</item>
|
||||
<item>@string/arrays__normal</item>
|
||||
<item>@string/arrays__large</item>
|
||||
<item>@string/arrays__extra_large</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_message_font_size_values">
|
||||
<item>13</item>
|
||||
<item>16</item>
|
||||
<item>20</item>
|
||||
<item>30</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_notification_priority_entries">
|
||||
<item>@string/arrays__default</item>
|
||||
<item>@string/arrays__high</item>
|
||||
<item>@string/arrays__max</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_notification_priority_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
301
libsession/src/main/res/values/attrs.xml
Normal file
301
libsession/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,301 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="theme_type" format="string"/>
|
||||
|
||||
<attr name="attachment_type_selector_background" format="color"/>
|
||||
<attr name="attachment_type_selector_hide_button_background" format="color"/>
|
||||
<attr name="attachment_document_icon_small" format="reference" />
|
||||
<attr name="attachment_document_icon_large" format="reference" />
|
||||
<attr name="attachment_document_extension_text_color" format="color" />
|
||||
|
||||
<attr name="conversation_list_item_background" format="reference"/>
|
||||
<attr name="conversation_list_item_contact_color" format="reference|color"/>
|
||||
<attr name="conversation_list_item_subject_color" format="reference|color"/>
|
||||
<attr name="conversation_list_item_delivery_icon_color" format="reference|color"/>
|
||||
<attr name="conversation_list_item_date_color" format="reference|color"/>
|
||||
<attr name="conversation_list_item_unread_color" format="reference|color"/>
|
||||
<attr name="conversation_list_item_unread_background" format="reference"/>
|
||||
<attr name="conversation_list_item_divider" format="reference"/>
|
||||
<attr name="conversation_list_toolbar_background" format="reference"/>
|
||||
<attr name="conversation_list_typing_tint" format="color"/>
|
||||
|
||||
<attr name="conversation_group_member_name" format="reference|color"/>
|
||||
|
||||
<attr name="fab_color" format="reference|color" />
|
||||
<attr name="lower_right_divet" format="reference" />
|
||||
|
||||
<attr name="ic_arrow_forward" format="reference" />
|
||||
<attr name="ic_visibility_on" format="reference" />
|
||||
<attr name="ic_visibility_off" format="reference" />
|
||||
|
||||
<attr name="conversation_background" format="reference|color"/>
|
||||
<attr name="conversation_editor_background" format="reference|color"/>
|
||||
<attr name="conversation_editor_text_color" format="reference|color"/>
|
||||
<attr name="conversation_input_background" format="reference"/>
|
||||
<attr name="conversation_input_inline_attach_icon_tint" format="reference"/>
|
||||
<attr name="conversation_transport_sms_indicator" format="reference"/>
|
||||
<attr name="conversation_transport_push_indicator" format="reference"/>
|
||||
<attr name="conversation_transport_popup_background" format="reference"/>
|
||||
<attr name="conversation_emoji_toggle" format="reference"/>
|
||||
<attr name="conversation_sticker_toggle" format="reference"/>
|
||||
<attr name="conversation_keyboard_toggle" format="reference"/>
|
||||
<attr name="conversation_attach_camera" format="reference"/>
|
||||
<attr name="conversation_attach_image" format="reference"/>
|
||||
<attr name="conversation_attach_video" format="reference"/>
|
||||
<attr name="conversation_attach_sound" format="reference"/>
|
||||
<attr name="conversation_attach_contact_info" format="reference"/>
|
||||
<attr name="conversation_attach" format="reference"/>
|
||||
<attr name="conversation_number_picker_text_color_normal" format="reference|color"/>
|
||||
<attr name="conversation_number_picker_text_color_selected" format="reference|color"/>
|
||||
<attr name="conversation_sticker_footer_text_color" format="reference|color"/>
|
||||
<attr name="conversation_sticker_footer_icon_color" format="reference|color"/>
|
||||
<attr name="conversation_sticker_author_color" format="reference|color"/>
|
||||
|
||||
<attr name="emoji_tab_strip_background" format="color" />
|
||||
<attr name="emoji_tab_indicator" format="color" />
|
||||
<attr name="emoji_tab_underline" format="color" />
|
||||
<attr name="emoji_tab_seperator" format="color" />
|
||||
<attr name="emoji_drawer_background" format="color" />
|
||||
<attr name="emoji_text_color" format="color" />
|
||||
|
||||
<attr name="emoji_category_recent" format="reference"/>
|
||||
<attr name="emoji_category_people" format="reference"/>
|
||||
<attr name="emoji_category_nature" format="reference"/>
|
||||
<attr name="emoji_category_foods" format="reference"/>
|
||||
<attr name="emoji_category_activity" format="reference"/>
|
||||
<attr name="emoji_category_places" format="reference"/>
|
||||
<attr name="emoji_category_objects" format="reference"/>
|
||||
<attr name="emoji_category_symbol" format="reference"/>
|
||||
<attr name="emoji_category_flags" format="reference"/>
|
||||
<attr name="emoji_category_emoticons" format="reference"/>
|
||||
<attr name="emoji_variation_selector_background" format="reference|color" />
|
||||
|
||||
<attr name="quick_camera_icon" format="reference"/>
|
||||
<attr name="quick_mic_icon" format="reference"/>
|
||||
|
||||
<attr name="conversation_item_bubble_background" format="reference|color"/>
|
||||
<attr name="conversation_item_sent_text_primary_color" format="reference|color"/>
|
||||
<attr name="conversation_item_sent_text_secondary_color" format="reference|color"/>
|
||||
<attr name="conversation_item_received_text_primary_color" format="reference|color"/>
|
||||
<attr name="conversation_item_received_text_secondary_color" format="reference|color"/>
|
||||
<attr name="conversation_item_sent_text_indicator_tab_color" format="reference|color"/>
|
||||
<attr name="conversation_item_sent_indicator_text_background" format="reference" />
|
||||
<attr name="conversation_item_sent_icon_color" format="color" />
|
||||
<attr name="conversation_item_sticky_date_background" format="reference" />
|
||||
<attr name="conversation_item_sticky_date_text_color" format="color" />
|
||||
<attr name="conversation_item_image_outline_color" format="color" />
|
||||
<attr name="conversation_item_audio_seek_bar_color_incoming" format="reference|color" />
|
||||
<attr name="conversation_item_audio_seek_bar_color_outgoing" format="reference|color" />
|
||||
<attr name="conversation_item_audio_seek_bar_background_color" format="reference|color" />
|
||||
|
||||
<attr name="dialog_info_icon" format="reference" />
|
||||
<attr name="dialog_alert_icon" format="reference" />
|
||||
<attr name="dialog_background_color" format="reference|color" />
|
||||
|
||||
<attr name="conversation_icon_attach_audio" format="reference"/>
|
||||
<attr name="conversation_icon_attach_video" format="reference" />
|
||||
|
||||
<attr name="device_link_item_card_background" format="reference|color" />
|
||||
|
||||
<attr name="linkpreview_background_color" format="color" />
|
||||
<attr name="linkpreview_primary_text_color" format="color" />
|
||||
<attr name="linkpreview_secondary_text_color" format="color" />
|
||||
<attr name="linkpreview_divider_color" format="color" />
|
||||
|
||||
<attr name="media_keyboard_button_color" format="color" />
|
||||
|
||||
<attr name="menu_popup_expand" format="reference"/>
|
||||
<attr name="menu_trash_icon" format="reference" />
|
||||
<attr name="menu_selectall_icon" format="reference" />
|
||||
<attr name="menu_split_icon" format="reference" />
|
||||
<attr name="menu_accept_icon" format="reference" />
|
||||
<attr name="menu_copy_icon" format="reference" />
|
||||
<attr name="menu_forward_icon" format="reference" />
|
||||
<attr name="menu_save_icon" format="reference" />
|
||||
<attr name="menu_reply_icon" format="reference" />
|
||||
<attr name="menu_block_icon" format="reference" />
|
||||
<attr name="menu_photo_library_icon" format="reference" />
|
||||
<attr name="menu_delete_icon" format="reference" />
|
||||
<attr name="menu_info_icon" format="reference" />
|
||||
|
||||
<attr name="pref_icon_tint" format="color"/>
|
||||
|
||||
<attr name="home_gradient_start" format="color|reference" />
|
||||
<attr name="home_gradient_end" format="color|reference" />
|
||||
|
||||
<attr name="message_received_background_color" format="color|reference" />
|
||||
<attr name="message_sent_background_color" format="color|reference" />
|
||||
|
||||
<attr name="verification_background" format="color"/>
|
||||
|
||||
<attr name="media_overview_toolbar_background" format="color"/>
|
||||
<attr name="media_overview_header_foreground" format="color"/>
|
||||
|
||||
<attr name="shared_contact_details_header_background" format="color"/>
|
||||
<attr name="shared_contact_details_titlebar" format="color"/>
|
||||
<attr name="shared_contact_item_button_color" format="color"/>
|
||||
|
||||
<attr name="sticker_management_icon" format="reference" />
|
||||
<attr name="sticker_management_divider_color" format="color" />
|
||||
<attr name="sticker_management_empty_background_color" format="color" />
|
||||
<attr name="sticker_management_action_button_color" format="color" />
|
||||
<attr name="sticker_popup_background" format="color" />
|
||||
<attr name="sticker_preview_toolbar_background" format="color" />
|
||||
<attr name="sticker_preview_status_bar_color" format="color" />
|
||||
<attr name="sticker_view_missing_background" format="reference|color" />
|
||||
|
||||
<attr name="tooltip_default_color" format="reference|color" />
|
||||
|
||||
<declare-styleable name="CustomDefaultPreference">
|
||||
<attr name="custom_pref_toggle" format="string"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AvatarImageView">
|
||||
<attr name="inverted" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ThumbnailView">
|
||||
<attr name="minWidth" format="dimension" />
|
||||
<attr name="maxWidth" format="dimension" />
|
||||
<attr name="minHeight" format="dimension" />
|
||||
<attr name="maxHeight" format="dimension" />
|
||||
<attr name="thumbnail_radius" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DeliveryStatusView">
|
||||
<attr name="iconColor" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AlertView">
|
||||
<attr name="useSmallIcon" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="MessageAudioView">
|
||||
<attr name="widgetBackground" format="color"/>
|
||||
<attr name="foregroundTintColor" format="color" />
|
||||
<attr name="waveformFillColor" format="reference|color" />
|
||||
<attr name="waveformBackgroundColor" format="reference|color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="CircleColorImageView">
|
||||
<attr name="circleColor" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ShapeScrim">
|
||||
<attr name="shape" format="string"/>
|
||||
<attr name="radius" format="float"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="CameraView">
|
||||
<attr name="camera" format="integer"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="HourglassView">
|
||||
<attr name="full" format="reference"/>
|
||||
<attr name="empty" format="reference"/>
|
||||
<attr name="tint" format="color"/>
|
||||
<attr name="percentage" format="integer"/>
|
||||
<attr name="offset" format="integer"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SquareFrameLayout">
|
||||
<attr name="square_height" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="VerticalSlideColorPicker">
|
||||
<attr name="pickerBorderColor" format="color" />
|
||||
<attr name="pickerBorderWidth" format="dimension" />
|
||||
<attr name="pickerColors" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="EmojiTextView">
|
||||
<attr name="scaleEmojis" format="boolean" />
|
||||
<attr name="emoji_maxLength" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ColorPickerPreference">
|
||||
<attr name="currentColor" format="reference" />
|
||||
<attr name="colors" format="reference" />
|
||||
<attr name="sortColors" format="boolean|reference" />
|
||||
<attr name="colorDescriptions" format="reference" />
|
||||
<attr name="columns" format="integer|reference" />
|
||||
<attr name="colorSize" format="enum|reference">
|
||||
<enum name="large" value="1" />
|
||||
<enum name="small" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="VerificationCodeView">
|
||||
<attr name="vcv_spacing" format="dimension"/>
|
||||
<attr name="vcv_inputWidth" format="dimension"/>
|
||||
<attr name="vcv_inputHeight" format="dimension"/>
|
||||
<attr name="vcv_inputColor" format="color"/>
|
||||
<attr name="vcv_textSize" format="dimension"/>
|
||||
<attr name="vcv_textColor" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="QuoteView">
|
||||
<attr name="message_type" format="enum">
|
||||
<enum name="preview" value="0" />
|
||||
<enum name="outgoing" value="1" />
|
||||
<enum name="incoming" value="2" />
|
||||
</attr>
|
||||
<attr name="quote_colorPrimary" format="color" />
|
||||
<attr name="quote_colorSecondary" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="LinkPreviewView">
|
||||
<attr name="linkpreview_type" format="enum">
|
||||
<enum name="conversation" value="0" />
|
||||
<enum name="compose" value="1" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DocumentView">
|
||||
<attr name="doc_titleColor" format="color" />
|
||||
<attr name="doc_captionColor" format="color" />
|
||||
<attr name="doc_downloadButtonTint" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ConversationItemFooter">
|
||||
<attr name="footer_text_color" format="color" />
|
||||
<attr name="footer_icon_color" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ConversationItemThumbnail">
|
||||
<attr name="conversationThumbnail_minWidth" format="dimension" />
|
||||
<attr name="conversationThumbnail_maxWidth" format="dimension" />
|
||||
<attr name="conversationThumbnail_minHeight" format="dimension" />
|
||||
<attr name="conversationThumbnail_maxHeight" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TypingIndicatorView">
|
||||
<attr name="typingIndicator_tint" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="MaxHeightScrollView">
|
||||
<attr name="scrollView_maxHeight" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="LabeledEditText">
|
||||
<attr name="labeledEditText_label" format="string" />
|
||||
<attr name="labeledEditText_background" format="color" />
|
||||
<attr name="labeledEditText_textLayout" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="WaveformSeekBar">
|
||||
<attr name="progress" format="float"/>
|
||||
<attr name="bar_width" format="dimension"/>
|
||||
<attr name="bar_gap" format="dimension"/>
|
||||
<attr name="bar_min_height" format="dimension"/>
|
||||
<attr name="bar_corner_radius" format="dimension"/>
|
||||
<attr name="bar_background_color" format="color"/>
|
||||
<attr name="bar_progress_color" format="color"/>
|
||||
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
|
||||
<attr name="bar_gravity" format="enum">
|
||||
<enum name="top" value="1" />
|
||||
<enum name="center" value="2" />
|
||||
<enum name="bottom" value="3" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
120
libsession/src/main/res/values/colors.xml
Normal file
120
libsession/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
|
||||
<!-- Session -->
|
||||
<color name="accent">#00F782</color>
|
||||
<color name="accent_alpha50">#8000F782</color>
|
||||
<color name="text">#FFFFFF</color>
|
||||
<color name="destructive">#FF453A</color>
|
||||
<color name="unimportant">#D8D8D8</color>
|
||||
<color name="profile_picture_border">#23FFFFFF</color>
|
||||
<color name="profile_picture_background">#353535</color>
|
||||
<color name="border">#979797</color>
|
||||
<color name="cell_background">#1B1B1B</color>
|
||||
<color name="cell_selected">#0C0C0C</color>
|
||||
<color name="action_bar_background">#171717</color>
|
||||
<color name="navigation_bar_background">#121212</color>
|
||||
<color name="separator">#36383C</color>
|
||||
<color name="unimportant_button_background">#323232</color>
|
||||
<color name="dialog_background">#101011</color>
|
||||
<color name="dialog_border">#212121</color>
|
||||
<color name="unimportant_dialog_button_background">@color/unimportant_button_background</color>
|
||||
<color name="fake_chat_bubble_background">#3F4146</color>
|
||||
<color name="fake_chat_bubble_text">#000000</color>
|
||||
<color name="app_icon_background">#333132</color>
|
||||
<color name="progress_bar_background">#0AFFFFFF</color>
|
||||
<color name="compose_view_background">#1B1B1B</color>
|
||||
<color name="compose_text_view_background">#141414</color>
|
||||
<color name="quote_not_found_background">#80FFFFFF</color>
|
||||
<color name="new_conversation_button_collapsed_background">#1F1F1F</color>
|
||||
<color name="new_conversation_button_shadow">#077C44</color>
|
||||
<color name="pn_option_background">#1B1B1B</color>
|
||||
<color name="pn_option_border">#212121</color>
|
||||
<color name="paths_building">#FFCE3A</color>
|
||||
|
||||
<array name="profile_picture_placeholder_colors">
|
||||
<item>#5ff8b0</item>
|
||||
<item>#26cdb9</item>
|
||||
<item>#f3c615</item>
|
||||
<item>#fcac5a</item>
|
||||
</array>
|
||||
<!-- Session -->
|
||||
|
||||
<!-- Loki -->
|
||||
<color name="loki_green">#78be20</color>
|
||||
<color name="loki_dark_green">#419B41</color>
|
||||
<color name="loki_darkest_gray">#0a0a0a</color>
|
||||
<!-- Loki -->
|
||||
|
||||
<color name="signal_primary">@color/accent</color>
|
||||
<color name="signal_primary_dark">@color/accent</color>
|
||||
<color name="signal_primary_alpha_focus">#882090ea</color>
|
||||
|
||||
<color name="textsecure_primary">@color/signal_primary</color>
|
||||
<color name="textsecure_primary_dark">@color/signal_primary_dark</color>
|
||||
|
||||
<color name="white">#ffffffff</color>
|
||||
<color name="black">#ff000000</color>
|
||||
<color name="gray5">#ffeeeeee</color>
|
||||
<color name="gray10">#ffdddddd</color>
|
||||
<color name="gray12">#ffe0e0e0</color>
|
||||
<color name="gray13">#ffababab</color>
|
||||
<color name="gray20">#ffcccccc</color>
|
||||
<color name="gray27">#ffbbbbbb</color>
|
||||
<color name="gray50">#ff808080</color>
|
||||
<color name="gray65">#ff595959</color>
|
||||
<color name="gray70">#ff4d4d4d</color>
|
||||
<color name="gray78">#ff383838</color>
|
||||
<color name="gray95">#ff111111</color>
|
||||
|
||||
<color name="transparent_black_05">#05000000</color>
|
||||
<color name="transparent_black_10">#10000000</color>
|
||||
<color name="transparent_black_20">#20000000</color>
|
||||
<color name="transparent_black_30">#30000000</color>
|
||||
<color name="transparent_black_40">#40000000</color>
|
||||
<color name="transparent_black_70">#70000000</color>
|
||||
<color name="transparent_black_90">#90000000</color>
|
||||
|
||||
<color name="transparent_white_05">#05ffffff</color>
|
||||
<color name="transparent_white_10">#10ffffff</color>
|
||||
<color name="transparent_white_20">#20ffffff</color>
|
||||
<color name="transparent_white_30">#30ffffff</color>
|
||||
<color name="transparent_white_40">#40ffffff</color>
|
||||
<color name="transparent_white_60">#60ffffff</color>
|
||||
<color name="transparent_white_70">#70ffffff</color>
|
||||
<color name="transparent_white_aa">#aaffffff</color>
|
||||
<color name="transparent_white_bb">#bbffffff</color>
|
||||
<color name="transparent_white_dd">#ddffffff</color>
|
||||
|
||||
<color name="conversation_compose_divider">#32000000</color>
|
||||
|
||||
<color name="action_mode_status_bar">@color/gray65</color>
|
||||
<color name="touch_highlight">#22000000</color>
|
||||
|
||||
<color name="device_link_item_background_light">#ffffffff</color>
|
||||
<color name="device_link_item_background_dark">#ff333333</color>
|
||||
|
||||
<color name="import_export_item_background_light">#ffeeeeee</color>
|
||||
<color name="import_export_item_background_dark">#ff333333</color>
|
||||
<color name="import_export_item_background_shadow_light">#ffd5d5d5</color>
|
||||
<color name="import_export_item_background_shadow_dark">#ff222222</color>
|
||||
<color name="import_export_touch_highlight_light">#400099cc</color>
|
||||
<color name="import_export_touch_highlight_dark">#40ffffff</color>
|
||||
|
||||
<color name="StickerPreviewActivity_remove_button_color">@color/conversation_crimson</color>
|
||||
<color name="StickerPreviewActivity_install_button_color">@color/core_blue</color>
|
||||
|
||||
<color name="sticker_selected_color">#99ffffff</color>
|
||||
<color name="transparent">#00FFFFFF</color>
|
||||
<color name="transparent_black">#00000000</color>
|
||||
|
||||
<color name="MediaOverview_Media_selected_overlay">#88000000</color>
|
||||
|
||||
<color name="logsubmit_confirmation_background">#44ff2d00</color>
|
||||
|
||||
<color name="avatar_background">@color/transparent_black_90</color>
|
||||
|
||||
<color name="default_background_start">#121212</color>
|
||||
<color name="default_background_end">#171717</color>
|
||||
|
||||
</resources>
|
54
libsession/src/main/res/values/conversation_colors.xml
Normal file
54
libsession/src/main/res/values/conversation_colors.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="conversation_crimson">#cc163d</color>
|
||||
<color name="conversation_crimson_tint">#eda6ae</color>
|
||||
<color name="conversation_crimson_shade">#8a0f29</color>
|
||||
|
||||
<color name="conversation_vermillion">#c73800</color>
|
||||
<color name="conversation_vermillion_tint">#eba78e</color>
|
||||
<color name="conversation_vermillion_shade">#872600</color>
|
||||
|
||||
<color name="conversation_burlap">#756c53</color>
|
||||
<color name="conversation_burlap_tint">#c4b997</color>
|
||||
<color name="conversation_burlap_shade">#58513c</color>
|
||||
|
||||
<color name="conversation_forest">#3b7845</color>
|
||||
<color name="conversation_forest_tint">#8fcc9a</color>
|
||||
<color name="conversation_forest_shade">#2b5934</color>
|
||||
|
||||
<color name="conversation_wintergreen">#1c8260</color>
|
||||
<color name="conversation_wintergreen_tint">#9bcfbd</color>
|
||||
<color name="conversation_wintergreen_shade">#36544a</color>
|
||||
|
||||
<color name="conversation_teal">#067589</color>
|
||||
<color name="conversation_teal_tint">#a5cad5</color>
|
||||
<color name="conversation_teal_shade">#055968</color>
|
||||
|
||||
<color name="conversation_blue">#336ba3</color>
|
||||
<color name="conversation_blue_tint">#adc8e1</color>
|
||||
<color name="conversation_blue_shade">#285480</color>
|
||||
|
||||
<color name="conversation_indigo">#5951c8</color>
|
||||
<color name="conversation_indigo_tint">#c2c1e8</color>
|
||||
<color name="conversation_indigo_shade">#4840a0</color>
|
||||
|
||||
<color name="conversation_violet">#862caf</color>
|
||||
<color name="conversation_violet_tint">#cdaddc</color>
|
||||
<color name="conversation_violet_shade">#6b248a</color>
|
||||
|
||||
<color name="conversation_plumb">#a23474</color>
|
||||
<color name="conversation_plumb_tint">#dcb2ca</color>
|
||||
<color name="conversation_plumb_shade">#881b5b</color>
|
||||
|
||||
<color name="conversation_taupe">#895d66</color>
|
||||
<color name="conversation_taupe_tint">#cfb5bb</color>
|
||||
<color name="conversation_taupe_shade">#6a4e54</color>
|
||||
|
||||
<color name="conversation_steel">#6b6b78</color>
|
||||
<color name="conversation_steel_tint">#bebec6</color>
|
||||
<color name="conversation_steel_shade">#5a5a63</color>
|
||||
|
||||
<color name="conversation_group">@color/textsecure_primary</color>
|
||||
<color name="conversation_group_tint">@color/textsecure_primary</color>
|
||||
<color name="conversation_group_shade">@color/textsecure_primary_dark</color>
|
||||
</resources>
|
21
libsession/src/main/res/values/core_colors.xml
Normal file
21
libsession/src/main/res/values/core_colors.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="core_blue">#5bca5b</color>
|
||||
<color name="core_green">#4caf50</color>
|
||||
<color name="core_red">#f44336</color>
|
||||
<color name="core_red_highlight">#ef5350</color>
|
||||
|
||||
<color name="core_white">#ffffff</color>
|
||||
<color name="core_black">#000000</color>
|
||||
|
||||
<color name="core_grey_02">#f8f9f9</color>
|
||||
<color name="core_grey_05">#eeefef</color>
|
||||
<color name="core_grey_15">#d5d6d6</color>
|
||||
<color name="core_grey_25">#bbbdbe</color>
|
||||
<color name="core_grey_45">#898a8c</color>
|
||||
<color name="core_grey_60">#6b6d70</color>
|
||||
<color name="core_grey_75">#3d3e44</color>
|
||||
<color name="core_grey_85">#23252a</color>
|
||||
<color name="core_grey_90">#17191d</color>
|
||||
<color name="core_grey_95">#0f1012</color>
|
||||
</resources>
|
10
libsession/src/main/res/values/crop_area_renderer.xml
Normal file
10
libsession/src/main/res/values/crop_area_renderer.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="crop_area_renderer_edge_size">32dp</dimen>
|
||||
<dimen name="crop_area_renderer_edge_thickness">2dp</dimen>
|
||||
|
||||
<color name="crop_area_renderer_edge_color">#ffffffff</color>
|
||||
<color name="crop_area_renderer_outer_color">#7f000000</color>
|
||||
|
||||
</resources>
|
174
libsession/src/main/res/values/dimens.xml
Normal file
174
libsession/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Session -->
|
||||
|
||||
<!-- Font Sizes -->
|
||||
<dimen name="very_small_font_size">12sp</dimen>
|
||||
<dimen name="small_font_size">15sp</dimen>
|
||||
<dimen name="medium_font_size">17sp</dimen>
|
||||
<dimen name="large_font_size">22sp</dimen>
|
||||
<dimen name="very_large_font_size">26sp</dimen>
|
||||
<dimen name="massive_font_size">50sp</dimen>
|
||||
|
||||
<!-- Element Sizes -->
|
||||
<dimen name="small_button_height">34dp</dimen>
|
||||
<dimen name="medium_button_height">38dp</dimen>
|
||||
<dimen name="medium_button_corner_radius">22dp</dimen>
|
||||
<dimen name="accent_line_thickness">4dp</dimen>
|
||||
<dimen name="very_small_profile_picture_size">26dp</dimen>
|
||||
<dimen name="small_profile_picture_size">36dp</dimen>
|
||||
<dimen name="medium_profile_picture_size">46dp</dimen>
|
||||
<dimen name="large_profile_picture_size">76dp</dimen>
|
||||
<dimen name="conversation_view_status_indicator_size">14dp</dimen>
|
||||
<dimen name="border_thickness">1dp</dimen>
|
||||
<dimen name="profile_picture_border_thickness">1dp</dimen>
|
||||
<dimen name="new_conversation_button_collapsed_size">60dp</dimen>
|
||||
<dimen name="new_conversation_button_expanded_size">72dp</dimen>
|
||||
<dimen name="tab_bar_height">36dp</dimen>
|
||||
<dimen name="text_view_corner_radius">8dp</dimen>
|
||||
<dimen name="fake_chat_view_bubble_width">224dp</dimen>
|
||||
<dimen name="fake_chat_view_bubble_corner_radius">10dp</dimen>
|
||||
<dimen name="fake_chat_view_height">250dp</dimen>
|
||||
<dimen name="setting_button_height">56dp</dimen>
|
||||
<dimen name="dialog_corner_radius">8dp</dimen>
|
||||
<dimen name="dialog_button_corner_radius">4dp</dimen>
|
||||
<dimen name="pn_option_corner_radius">8dp</dimen>
|
||||
<dimen name="path_status_view_size">8dp</dimen>
|
||||
<dimen name="path_row_height">56dp</dimen>
|
||||
<dimen name="path_row_dot_size">8dp</dimen>
|
||||
<dimen name="path_row_expanded_dot_size">16dp</dimen>
|
||||
|
||||
<!-- Distances -->
|
||||
<dimen name="small_spacing">8dp</dimen>
|
||||
<dimen name="medium_spacing">16dp</dimen>
|
||||
<dimen name="large_spacing">24dp</dimen>
|
||||
<dimen name="very_large_spacing">35dp</dimen>
|
||||
<dimen name="massive_spacing">64dp</dimen>
|
||||
<dimen name="new_conversation_button_bottom_offset">56dp</dimen>
|
||||
<dimen name="onboarding_button_bottom_offset">40dp</dimen>
|
||||
|
||||
<!-- Session -->
|
||||
|
||||
<dimen name="emoji_drawer_size">32sp</dimen>
|
||||
<dimen name="min_keyboard_size">50dp</dimen>
|
||||
<dimen name="default_custom_keyboard_size">220dp</dimen>
|
||||
<dimen name="min_custom_keyboard_size">110dp</dimen>
|
||||
<dimen name="min_custom_keyboard_top_margin_portrait">170dp</dimen>
|
||||
<dimen name="min_custom_keyboard_top_margin_landscape">50dp</dimen>
|
||||
<dimen name="emoji_drawer_item_padding">5dp</dimen>
|
||||
<dimen name="emoji_drawer_indicator_height">1.5dp</dimen>
|
||||
<dimen name="emoji_drawer_left_right_padding">5dp</dimen>
|
||||
<dimen name="emoji_drawer_item_width">48dp</dimen>
|
||||
<dimen name="conversation_item_body_text_size">16sp</dimen>
|
||||
<dimen name="conversation_item_date_text_size">12sp</dimen>
|
||||
<dimen name="transport_selection_popup_width">200sp</dimen>
|
||||
<dimen name="transport_selection_popup_xoff">2dp</dimen>
|
||||
<dimen name="transport_selection_popup_yoff">2dp</dimen>
|
||||
<dimen name="contact_photo_target_size">64dp</dimen>
|
||||
<dimen name="contact_selection_photo_size">50dp</dimen>
|
||||
|
||||
<dimen name="album_total_width">210dp</dimen>
|
||||
<dimen name="album_2_total_height">105dp</dimen>
|
||||
<dimen name="album_2_cell_width">104dp</dimen>
|
||||
<dimen name="album_3_total_height">140dp</dimen>
|
||||
<dimen name="album_3_cell_width_big">139dp</dimen>
|
||||
<dimen name="album_3_cell_size_small">69dp</dimen>
|
||||
<dimen name="album_4_total_height">210dp</dimen>
|
||||
<dimen name="album_4_cell_size">104dp</dimen>
|
||||
<dimen name="album_5_total_height">175dp</dimen>
|
||||
<dimen name="album_5_cell_size_big">104dp</dimen>
|
||||
<dimen name="album_5_cell_size_small">69dp</dimen>
|
||||
|
||||
<dimen name="message_corner_radius">10dp</dimen>
|
||||
<dimen name="message_corner_collapse_radius">4dp</dimen>
|
||||
<dimen name="message_bubble_corner_radius">2dp</dimen>
|
||||
<dimen name="message_bubble_shadow_distance">1.5dp</dimen>
|
||||
<dimen name="message_bubble_horizontal_padding">@dimen/medium_spacing</dimen>
|
||||
<dimen name="message_bubble_top_padding">@dimen/medium_spacing</dimen>
|
||||
<dimen name="message_bubble_collapsed_footer_padding">@dimen/small_spacing</dimen>
|
||||
<dimen name="message_bubble_edge_margin">32dp</dimen>
|
||||
<dimen name="message_bubble_bottom_padding">@dimen/medium_spacing</dimen>
|
||||
<dimen name="media_bubble_remove_button_size">24dp</dimen>
|
||||
<dimen name="media_bubble_edit_button_size">24dp</dimen>
|
||||
<dimen name="media_bubble_default_dimens">210dp</dimen>
|
||||
<dimen name="media_bubble_min_width">150dp</dimen>
|
||||
<dimen name="media_bubble_max_width">240dp</dimen>
|
||||
<dimen name="media_bubble_min_height">100dp</dimen>
|
||||
<dimen name="media_bubble_max_height">320dp</dimen>
|
||||
<dimen name="media_bubble_sticker_dimens">128dp</dimen>
|
||||
|
||||
<dimen name="media_picker_folder_width">175dp</dimen>
|
||||
<dimen name="media_picker_item_width">85dp</dimen>
|
||||
|
||||
<dimen name="media_keyboard_provider_icon_padding">5dp</dimen>
|
||||
<dimen name="media_keyboard_provider_icon_margin">4dp</dimen>
|
||||
|
||||
<dimen name="mediasend_progress_dialog_size">120dp</dimen>
|
||||
|
||||
<dimen name="thumbnail_default_radius">4dp</dimen>
|
||||
|
||||
<dimen name="conversation_compose_height">40dp</dimen>
|
||||
<dimen name="conversation_individual_right_gutter">@dimen/large_spacing</dimen>
|
||||
<dimen name="conversation_individual_left_gutter">@dimen/large_spacing</dimen>
|
||||
<dimen name="conversation_group_left_gutter">60dp</dimen>
|
||||
<dimen name="conversation_vertical_message_spacing_default">8dp</dimen>
|
||||
<dimen name="conversation_vertical_message_spacing_collapse">1dp</dimen>
|
||||
|
||||
<dimen name="conversation_item_avatar_size">36dp</dimen>
|
||||
|
||||
<dimen name="quote_corner_radius_large">10dp</dimen>
|
||||
<dimen name="quote_corner_radius_bottom">2dp</dimen>
|
||||
<dimen name="quote_corner_radius_preview">16dp</dimen>
|
||||
|
||||
<integer name="media_overview_cols">3</integer>
|
||||
<dimen name="message_details_table_row_pad">10dp</dimen>
|
||||
|
||||
<dimen name="quick_camera_shutter_ring_size">52dp</dimen>
|
||||
|
||||
<dimen name="sticker_page_item_padding">8dp</dimen>
|
||||
<dimen name="sticker_page_item_divisor">88dp</dimen>
|
||||
<dimen name="sticker_page_item_multiplier">8dp</dimen>
|
||||
<dimen name="sticker_preview_sticker_size">96dp</dimen>
|
||||
|
||||
<dimen name="sticker_management_horizontal_margin">16dp</dimen>
|
||||
|
||||
<dimen name="tooltip_popup_margin">8dp</dimen>
|
||||
|
||||
<dimen name="transfer_controls_expanded_width">150dp</dimen>
|
||||
<dimen name="transfer_controls_contracted_width">70dp</dimen>
|
||||
<dimen name="conversation_list_fragment_archive_padding">16dp</dimen>
|
||||
<dimen name="contact_selection_actions_tap_area">10dp</dimen>
|
||||
|
||||
<dimen name="unread_count_bubble_radius">13sp</dimen>
|
||||
<dimen name="unread_count_bubble_diameter">26sp</dimen>
|
||||
|
||||
<!-- RedPhone -->
|
||||
|
||||
<!-- Height of the main row of in-call buttons. -->
|
||||
|
||||
<!-- Padding at the left and right edges of the incall_touch_ui button
|
||||
cluster. This padding is necessary because we can't allow the
|
||||
buttons to be very close to the edges of the screen, due to the
|
||||
risk of false touches (from your finger wrapping around while
|
||||
holding the phone, *before* moving it up to your face and having
|
||||
the prox sensor kick in.) -->
|
||||
|
||||
<dimen name="onboarding_title_size">34sp</dimen>
|
||||
<dimen name="onboarding_subtitle_size">20sp</dimen>
|
||||
|
||||
<dimen name="scribble_stroke_size">2dp</dimen>
|
||||
|
||||
<dimen name="floating_action_button_margin">16dp</dimen>
|
||||
|
||||
<dimen name="alertview_small_icon_size">14dp</dimen>
|
||||
|
||||
<dimen name="recording_voice_lock_target">-96dp</dimen>
|
||||
|
||||
<dimen name="default_margin">16dp</dimen>
|
||||
<dimen name="drawable_padding">24dp</dimen>
|
||||
<dimen name="text_size">16sp</dimen>
|
||||
<dimen name="normal_padding">16dp</dimen>
|
||||
<dimen name="action_item_height">56dp</dimen>
|
||||
|
||||
</resources>
|
3
libsession/src/main/res/values/emoji.xml
Normal file
3
libsession/src/main/res/values/emoji.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
36
libsession/src/main/res/values/google-playstore-strings.xml
Normal file
36
libsession/src/main/res/values/google-playstore-strings.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
These strings are the source of the TextSecure Google Play Store description.
|
||||
https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms&hl=en
|
||||
|
||||
The sparse numbering of the string variables allows inserting additional lines
|
||||
later without the need to renumber existing strings.
|
||||
|
||||
How these strings are translated:
|
||||
see https://github.com/signalapp/TextSecure/wiki/Translation-workflow
|
||||
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<string name="PlaystoreDescription_para_0100">TextSecure Private Messenger</string>
|
||||
<string name="PlaystoreDescription_para_0200">TextSecure is a messaging app that allows you to take back your privacy while easily communicating with friends.</string>
|
||||
|
||||
<string name="PlaystoreDescription_para_0300">Using TextSecure, you can communicate instantly while avoiding SMS fees, create groups so that you can chat in real time with all your friends at once, and share media or attachments all with complete privacy. The server never has access to any of your communication and never stores any of your data.</string>
|
||||
|
||||
<string name="PlaystoreDescription_para_0400">* Private. TextSecure uses an advanced end to end encryption protocol that provides privacy for every message every time.</string>
|
||||
<string name="PlaystoreDescription_para_0500">* Open Source. TextSecure is Free and Open Source, enabling anyone to verify its security by auditing the code. TextSecure is the only private messenger that uses open source peer-reviewed cryptographic protocols to keep your messages safe.</string>
|
||||
<string name="PlaystoreDescription_para_0600">* Group Chat. TextSecure allows you to create encrypted groups so you can have private conversations with all your friends at once. Not only are the messages encrypted, but the TextSecure server never has access to any group metadata such as the membership list, group title, or group icon.</string>
|
||||
<string name="PlaystoreDescription_para_0700">* Fast. The TextSecure protocol is designed to operate in the most constrained environment possible. Using TextSecure, messages are instantly delivered to friends.</string>
|
||||
|
||||
<string name="PlaystoreDescription_para_0800">Please file any bugs, issues, or feature requests at:</string>
|
||||
<string name="PlaystoreDescription_para_0900">https://github.com/signalapp/textsecure/issues</string>
|
||||
|
||||
<string name="PlaystoreDescription_para_1000">More details:</string>
|
||||
<string name="PlaystoreDescription_para_1100">http://www.whispersystems.org/#encrypted_texts</string>
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
</resources>
|
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#333132</color>
|
||||
</resources>
|
6
libsession/src/main/res/values/ids.xml
Normal file
6
libsession/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="holder_tag"/>
|
||||
<item type="id" name="contact_info_tag"/>
|
||||
<item type="id" name="motion_view_edittext"/>
|
||||
</resources>
|
4
libsession/src/main/res/values/integers.xml
Normal file
4
libsession/src/main/res/values/integers.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="play_button_animation_duration">300</integer>
|
||||
</resources>
|
278
libsession/src/main/res/values/material_colors.xml
Normal file
278
libsession/src/main/res/values/material_colors.xml
Normal file
@ -0,0 +1,278 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="red_50">#FFEBEE</color>
|
||||
<color name="red_100">#FFCDD2</color>
|
||||
<color name="red_200">#EF9A9A</color>
|
||||
<color name="red_300">#E57373</color>
|
||||
<color name="red_400">#EF5350</color>
|
||||
<color name="red_500">#F44336</color>
|
||||
<color name="red_600">#E53935</color>
|
||||
<color name="red_700">#D32F2F</color>
|
||||
<color name="red_800">#C62828</color>
|
||||
<color name="red_900">#B71C1C</color>
|
||||
<color name="red_A100">#FF8A80</color>
|
||||
<color name="red_A200">#FF5252</color>
|
||||
<color name="red_A400">#FF1744</color>
|
||||
<color name="red_A700">#D50000</color>
|
||||
|
||||
<color name="deep_purple_50">#EDE7F6</color>
|
||||
<color name="deep_purple_100">#D1C4E9</color>
|
||||
<color name="deep_purple_200">#B39DDB</color>
|
||||
<color name="deep_purple_300">#9575CD</color>
|
||||
<color name="deep_purple_400">#7E57C2</color>
|
||||
<color name="deep_purple_500">#673AB7</color>
|
||||
<color name="deep_purple_600">#5E35B1</color>
|
||||
<color name="deep_purple_700">#512DA8</color>
|
||||
<color name="deep_purple_800">#4527A0</color>
|
||||
<color name="deep_purple_900">#311B92</color>
|
||||
<color name="deep_purple_A100">#B388FF</color>
|
||||
<color name="deep_purple_A200">#7C4DFF</color>
|
||||
<color name="deep_purple_A400">#651FFF</color>
|
||||
<color name="deep_purple_A700">#6200EA</color>
|
||||
|
||||
<color name="light_blue_50">#E1F5FE</color>
|
||||
<color name="light_blue_100">#B3E5FC</color>
|
||||
<color name="light_blue_200">#81D4FA</color>
|
||||
<color name="light_blue_300">#4FC3F7</color>
|
||||
<color name="light_blue_400">#29B6F6</color>
|
||||
<color name="light_blue_500">#03A9F4</color>
|
||||
<color name="light_blue_600">#039BE5</color>
|
||||
<color name="light_blue_700">#0288D1</color>
|
||||
<color name="light_blue_800">#0277BD</color>
|
||||
<color name="light_blue_900">#01579B</color>
|
||||
<color name="light_blue_A100">#80D8FF</color>
|
||||
<color name="light_blue_A200">#40C4FF</color>
|
||||
<color name="light_blue_A400">#00B0FF</color>
|
||||
<color name="light_blue_A700">#0091EA</color>
|
||||
|
||||
<color name="green_50">#E8F5E9</color>
|
||||
<color name="green_100">#C8E6C9</color>
|
||||
<color name="green_200">#A5D6A7</color>
|
||||
<color name="green_300">#81C784</color>
|
||||
<color name="green_400">#66BB6A</color>
|
||||
<color name="green_500">#4CAF50</color>
|
||||
<color name="green_600">#43A047</color>
|
||||
<color name="green_700">#388E3C</color>
|
||||
<color name="green_800">#2E7D32</color>
|
||||
<color name="green_900">#1B5E20</color>
|
||||
<color name="green_A100">#B9F6CA</color>
|
||||
<color name="green_A200">#69F0AE</color>
|
||||
<color name="green_A400">#00E676</color>
|
||||
<color name="green_A700">#00C853</color>
|
||||
|
||||
<color name="yellow_50">#FFFDE7</color>
|
||||
<color name="yellow_100">#FFF9C4</color>
|
||||
<color name="yellow_200">#FFF59D</color>
|
||||
<color name="yellow_300">#FFF176</color>
|
||||
<color name="yellow_400">#FFEE58</color>
|
||||
<color name="yellow_500">#FFEB3B</color>
|
||||
<color name="yellow_600">#FDD835</color>
|
||||
<color name="yellow_700">#FBC02D</color>
|
||||
<color name="yellow_800">#F9A825</color>
|
||||
<color name="yellow_900">#F57F17</color>
|
||||
<color name="yellow_A100">#FFFF8D</color>
|
||||
<color name="yellow_A200">#FFFF00</color>
|
||||
<color name="yellow_A400">#FFEA00</color>
|
||||
<color name="yellow_A700">#FFD600</color>
|
||||
|
||||
<color name="deep_orange_50">#FBE9E7</color>
|
||||
<color name="deep_orange_100">#FFCCBC</color>
|
||||
<color name="deep_orange_200">#FFAB91</color>
|
||||
<color name="deep_orange_300">#FF8A65</color>
|
||||
<color name="deep_orange_400">#FF7043</color>
|
||||
<color name="deep_orange_500">#FF5722</color>
|
||||
<color name="deep_orange_600">#F4511E</color>
|
||||
<color name="deep_orange_700">#E64A19</color>
|
||||
<color name="deep_orange_800">#D84315</color>
|
||||
<color name="deep_orange_900">#BF360C</color>
|
||||
<color name="deep_orange_A100">#FF9E80</color>
|
||||
<color name="deep_orange_A200">#FF6E40</color>
|
||||
<color name="deep_orange_A400">#FF3D00</color>
|
||||
<color name="deep_orange_A700">#DD2C00</color>
|
||||
|
||||
<color name="blue_grey_50">#ECEFF1</color>
|
||||
<color name="blue_grey_100">#CFD8DC</color>
|
||||
<color name="blue_grey_200">#B0BEC5</color>
|
||||
<color name="blue_grey_300">#90A4AE</color>
|
||||
<color name="blue_grey_400">#78909C</color>
|
||||
<color name="blue_grey_500">#607D8B</color>
|
||||
<color name="blue_grey_600">#546E7A</color>
|
||||
<color name="blue_grey_700">#455A64</color>
|
||||
<color name="blue_grey_800">#37474F</color>
|
||||
<color name="blue_grey_900">#263238</color>
|
||||
|
||||
<color name="pink_50">#FCE4EC</color>
|
||||
<color name="pink_100">#F8BBD0</color>
|
||||
<color name="pink_200">#F48FB1</color>
|
||||
<color name="pink_300">#F06292</color>
|
||||
<color name="pink_400">#EC407A</color>
|
||||
<color name="pink_500">#E91E63</color>
|
||||
<color name="pink_600">#D81B60</color>
|
||||
<color name="pink_700">#C2185B</color>
|
||||
<color name="pink_800">#AD1457</color>
|
||||
<color name="pink_900">#880E4F</color>
|
||||
<color name="pink_A100">#FF80AB</color>
|
||||
<color name="pink_A200">#FF4081</color>
|
||||
<color name="pink_A400">#F50057</color>
|
||||
<color name="pink_A700">#C51162</color>
|
||||
|
||||
<color name="indigo_50">#E8EAF6</color>
|
||||
<color name="indigo_100">#C5CAE9</color>
|
||||
<color name="indigo_200">#9FA8DA</color>
|
||||
<color name="indigo_300">#7986CB</color>
|
||||
<color name="indigo_400">#5C6BC0</color>
|
||||
<color name="indigo_500">#3F51B5</color>
|
||||
<color name="indigo_600">#3949AB</color>
|
||||
<color name="indigo_700">#303F9F</color>
|
||||
<color name="indigo_800">#283593</color>
|
||||
<color name="indigo_900">#1A237E</color>
|
||||
<color name="indigo_A100">#8C9EFF</color>
|
||||
<color name="indigo_A200">#536DFE</color>
|
||||
<color name="indigo_A400">#3D5AFE</color>
|
||||
<color name="indigo_A700">#304FFE</color>
|
||||
|
||||
<color name="cyan_50">#E0F7FA</color>
|
||||
<color name="cyan_100">#B2EBF2</color>
|
||||
<color name="cyan_200">#80DEEA</color>
|
||||
<color name="cyan_300">#4DD0E1</color>
|
||||
<color name="cyan_400">#26C6DA</color>
|
||||
<color name="cyan_500">#00BCD4</color>
|
||||
<color name="cyan_600">#00ACC1</color>
|
||||
<color name="cyan_700">#0097A7</color>
|
||||
<color name="cyan_800">#00838F</color>
|
||||
<color name="cyan_900">#006064</color>
|
||||
<color name="cyan_A100">#84FFFF</color>
|
||||
<color name="cyan_A200">#18FFFF</color>
|
||||
<color name="cyan_A400">#00E5FF</color>
|
||||
<color name="cyan_A700">#00B8D4</color>
|
||||
|
||||
<color name="light_green_50">#F1F8E9</color>
|
||||
<color name="light_green_100">#DCEDC8</color>
|
||||
<color name="light_green_200">#C5E1A5</color>
|
||||
<color name="light_green_300">#AED581</color>
|
||||
<color name="light_green_400">#9CCC65</color>
|
||||
<color name="light_green_500">#8BC34A</color>
|
||||
<color name="light_green_600">#7CB342</color>
|
||||
<color name="light_green_700">#689F38</color>
|
||||
<color name="light_green_800">#558B2F</color>
|
||||
<color name="light_green_900">#33691E</color>
|
||||
<color name="light_green_A100">#CCFF90</color>
|
||||
<color name="light_green_A200">#B2FF59</color>
|
||||
<color name="light_green_A400">#76FF03</color>
|
||||
<color name="light_green_A700">#64DD17</color>
|
||||
|
||||
<color name="amber_50">#FFF8E1</color>
|
||||
<color name="amber_100">#FFECB3</color>
|
||||
<color name="amber_200">#FFE082</color>
|
||||
<color name="amber_300">#FFD54F</color>
|
||||
<color name="amber_400">#FFCA28</color>
|
||||
<color name="amber_500">#FFC107</color>
|
||||
<color name="amber_600">#FFB300</color>
|
||||
<color name="amber_700">#FFA000</color>
|
||||
<color name="amber_800">#FF8F00</color>
|
||||
<color name="amber_900">#FF6F00</color>
|
||||
<color name="amber_A100">#FFE57F</color>
|
||||
<color name="amber_A200">#FFD740</color>
|
||||
<color name="amber_A400">#FFC400</color>
|
||||
<color name="amber_A700">#FFAB00</color>
|
||||
|
||||
<color name="brown_50">#EFEBE9</color>
|
||||
<color name="brown_100">#D7CCC8</color>
|
||||
<color name="brown_200">#BCAAA4</color>
|
||||
<color name="brown_300">#A1887F</color>
|
||||
<color name="brown_400">#8D6E63</color>
|
||||
<color name="brown_500">#795548</color>
|
||||
<color name="brown_600">#6D4C41</color>
|
||||
<color name="brown_700">#5D4037</color>
|
||||
<color name="brown_800">#4E342E</color>
|
||||
<color name="brown_900">#3E2723</color>
|
||||
|
||||
<color name="purple_50">#F3E5F5</color>
|
||||
<color name="purple_100">#E1BEE7</color>
|
||||
<color name="purple_200">#CE93D8</color>
|
||||
<color name="purple_300">#BA68C8</color>
|
||||
<color name="purple_400">#AB47BC</color>
|
||||
<color name="purple_500">#9C27B0</color>
|
||||
<color name="purple_600">#8E24AA</color>
|
||||
<color name="purple_700">#7B1FA2</color>
|
||||
<color name="purple_800">#6A1B9A</color>
|
||||
<color name="purple_900">#4A148C</color>
|
||||
<color name="purple_A100">#EA80FC</color>
|
||||
<color name="purple_A200">#E040FB</color>
|
||||
<color name="purple_A400">#D500F9</color>
|
||||
<color name="purple_A700">#AA00FF</color>
|
||||
|
||||
<color name="blue_50">#E3F2FD</color>
|
||||
<color name="blue_100">#BBDEFB</color>
|
||||
<color name="blue_200">#90CAF9</color>
|
||||
<color name="blue_300">#64B5F6</color>
|
||||
<color name="blue_400">#42A5F5</color>
|
||||
<color name="blue_500">#2196F3</color>
|
||||
<color name="blue_600">#1E88E5</color>
|
||||
<color name="blue_700">#1976D2</color>
|
||||
<color name="blue_800">#1565C0</color>
|
||||
<color name="blue_900">#0D47A1</color>
|
||||
<color name="blue_A100">#82B1FF</color>
|
||||
<color name="blue_A200">#448AFF</color>
|
||||
<color name="blue_A400">#2979FF</color>
|
||||
<color name="blue_A700">#2962FF</color>
|
||||
|
||||
<color name="teal_50">#E0F2F1</color>
|
||||
<color name="teal_100">#B2DFDB</color>
|
||||
<color name="teal_200">#80CBC4</color>
|
||||
<color name="teal_300">#4DB6AC</color>
|
||||
<color name="teal_400">#26A69A</color>
|
||||
<color name="teal_500">#009688</color>
|
||||
<color name="teal_600">#00897B</color>
|
||||
<color name="teal_700">#00796B</color>
|
||||
<color name="teal_800">#00695C</color>
|
||||
<color name="teal_900">#004D40</color>
|
||||
<color name="teal_A100">#A7FFEB</color>
|
||||
<color name="teal_A200">#64FFDA</color>
|
||||
<color name="teal_A400">#1DE9B6</color>
|
||||
<color name="teal_A700">#00BFA5</color>
|
||||
|
||||
<color name="lime_50">#F9FBE7</color>
|
||||
<color name="lime_100">#F0F4C3</color>
|
||||
<color name="lime_200">#E6EE9C</color>
|
||||
<color name="lime_300">#DCE775</color>
|
||||
<color name="lime_400">#D4E157</color>
|
||||
<color name="lime_500">#CDDC39</color>
|
||||
<color name="lime_600">#C0CA33</color>
|
||||
<color name="lime_700">#AFB42B</color>
|
||||
<color name="lime_800">#9E9D24</color>
|
||||
<color name="lime_900">#827717</color>
|
||||
<color name="lime_A100">#F4FF81</color>
|
||||
<color name="lime_A200">#EEFF41</color>
|
||||
<color name="lime_A400">#C6FF00</color>
|
||||
<color name="lime_A700">#AEEA00</color>
|
||||
|
||||
<color name="orange_50">#FFF3E0</color>
|
||||
<color name="orange_100">#FFE0B2</color>
|
||||
<color name="orange_200">#FFCC80</color>
|
||||
<color name="orange_300">#FFB74D</color>
|
||||
<color name="orange_400">#FFA726</color>
|
||||
<color name="orange_500">#FF9800</color>
|
||||
<color name="orange_600">#FB8C00</color>
|
||||
<color name="orange_700">#F57C00</color>
|
||||
<color name="orange_800">#EF6C00</color>
|
||||
<color name="orange_900">#E65100</color>
|
||||
<color name="orange_A100">#FFD180</color>
|
||||
<color name="orange_A200">#FFAB40</color>
|
||||
<color name="orange_A400">#FF9100</color>
|
||||
<color name="orange_A700">#FF6D00</color>
|
||||
|
||||
<color name="grey_50">#FAFAFA</color>
|
||||
<color name="grey_100">#F5F5F5</color>
|
||||
<color name="grey_200">#EEEEEE</color>
|
||||
<color name="grey_300">#E0E0E0</color>
|
||||
<color name="grey_400">#BDBDBD</color>
|
||||
<color name="grey_500">#9E9E9E</color>
|
||||
<color name="grey_600">#757575</color>
|
||||
<color name="grey_700">#616161</color>
|
||||
<color name="grey_800">#424242</color>
|
||||
<color name="grey_900">#212121</color>
|
||||
|
||||
<color name="grey_400_transparent">#44BDBDBD</color>
|
||||
|
||||
</resources>
|
1878
libsession/src/main/res/values/strings.xml
Normal file
1878
libsession/src/main/res/values/strings.xml
Normal file
File diff suppressed because it is too large
Load Diff
441
libsession/src/main/res/values/styles.xml
Normal file
441
libsession/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,441 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Session -->
|
||||
<style name="Widget.Session.ActionBar" parent="Widget.AppCompat.Light.ActionBar.Solid">
|
||||
<item name="android:background">?colorPrimary</item>
|
||||
<item name="elevation">1dp</item>
|
||||
<item name="titleTextStyle">@style/TextAppearance.Session.DarkActionBar.TitleTextStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.ActionBar.Flat">
|
||||
<item name="android:elevation">0dp</item>
|
||||
<item name="elevation">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Session.DarkActionBar.TitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:textSize">@dimen/very_large_font_size</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.SearchView" parent="@style/Widget.AppCompat.SearchView">
|
||||
<item name="closeIcon">@drawable/ic_clear</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
|
||||
<item name="buttonBarNegativeButtonStyle">@style/Widget.Session.AlertDialog.NegativeButtonStyle</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.Session.AlertDialog.PositiveButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.BottomSheetDialog" parent="Widget.Design.BottomSheet.Modal">
|
||||
<item name="android:background">@drawable/default_bottom_sheet_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.AlertDialog.NegativeButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.AlertDialog.PositiveButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.AppBarLayout" parent="@style/Widget.Design.AppBarLayout">
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.TabBar" parent="Widget.AppCompat.ActionBar.TabBar">
|
||||
<item name="elevation">1dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.TabLayout" parent="Widget.Design.TabLayout">
|
||||
<item name="elevation">1dp</item>
|
||||
<item name="tabIndicatorColor">?colorAccent</item>
|
||||
<item name="tabSelectedTextColor">?colorAccent</item>>
|
||||
<item name="tabIndicatorHeight">@dimen/accent_line_thickness</item>
|
||||
<item name="tabRippleColor">@color/cell_selected</item>
|
||||
<item name="tabTextAppearance">@style/TextAppearance.Session.Tab</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Session.Tab" parent="TextAppearance.Design.Tab">
|
||||
<item name="android:textSize">@dimen/medium_font_size</item>
|
||||
<item name="textAllCaps">false</item>
|
||||
</style>
|
||||
|
||||
<!-- TODO These button styles require proper background selectors for up/down visual state. -->
|
||||
<style name="Widget.Session.Button.Common" parent="">
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textSize">@dimen/medium_font_size</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<!-- Helps to get rid of nasty elevation. We want a flat style here. -->
|
||||
<!-- https://stackoverflow.com/a/31003693/3802890 -->
|
||||
<item name ="android:stateListAnimator">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Common.ProminentFilled">
|
||||
<item name="android:background">@drawable/prominent_filled_button_medium_background</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:drawableTint" tools:ignore="NewApi">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Common.ProminentOutline">
|
||||
<item name="android:background">@drawable/prominent_outline_button_medium_background</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
<item name="android:drawableTint" tools:ignore="NewApi">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Common.UnimportantFilled">
|
||||
<item name="android:background">@drawable/unimportant_filled_button_medium_background</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:drawableTint" tools:ignore="NewApi">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Common.UnimportantOutline">
|
||||
<item name="android:background">@drawable/unimportant_outline_button_medium_background</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:drawableTint" tools:ignore="NewApi">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Dialog" parent="">
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textSize">@dimen/small_font_size</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Dialog.Unimportant">
|
||||
<item name="android:background">@drawable/unimportant_dialog_button_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Dialog.Prominent">
|
||||
<item name="android:background">@drawable/prominent_dialog_button_background</item>
|
||||
<item name="android:textColor">#222</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.Button.Dialog.Destructive">
|
||||
<item name="android:background">@drawable/destructive_dialog_button_background</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Session.EditText.Compose" parent="@style/Signal.Text.Body">
|
||||
<item name="android:padding">2dp</item>
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:maxLines">4</item>
|
||||
<item name="android:maxLength">65536</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:capitalize">sentences</item>
|
||||
<item name="android:autoText">true</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:imeOptions">flagNoEnterAction</item>
|
||||
<item name="android:inputType">textAutoCorrect|textCapSentences|textMultiLine</item>
|
||||
<item name="android:contentDescription">@string/conversation_activity__compose_description</item>
|
||||
<item name="android:textColorHint">?android:textColorHint</item>
|
||||
<item name="android:textSize">@dimen/small_font_size</item>
|
||||
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.Session.Settings" parent="PreferenceThemeOverlay.v14.Material" >
|
||||
<item name="android:textColor">@color/text</item>
|
||||
<item name="android:textColorSecondary">#99FFFFFF</item>
|
||||
<item name="android:textSize">@dimen/medium_font_size</item>
|
||||
</style>
|
||||
|
||||
<style name="SessionIDTextView">
|
||||
<item name="android:background">@drawable/session_id_text_view_background</item>
|
||||
<item name="android:padding">@dimen/medium_spacing</item>
|
||||
<item name="android:textSize">@dimen/large_font_size</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
<item name="android:fontFamily">@font/space_mono_regular</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
</style>
|
||||
|
||||
<style name="SessionEditText">
|
||||
<item name="android:background">@drawable/session_id_text_view_background</item>
|
||||
<item name="android:paddingLeft">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingTop">30dp</item>
|
||||
<item name="android:paddingRight">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingBottom">30dp</item>
|
||||
<item name="android:textSize">@dimen/small_font_size</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
</style>
|
||||
|
||||
<style name="SmallSessionEditText">
|
||||
<item name="android:background">@drawable/session_id_text_view_background</item>
|
||||
<item name="android:paddingLeft">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingTop">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingRight">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingBottom">@dimen/medium_spacing</item>
|
||||
<item name="android:textSize">@dimen/small_font_size</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
<item name="android:textCursorDrawable">@drawable/session_edit_text_cursor</item>
|
||||
<item name="android:textAlignment">viewStart</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
</style>
|
||||
|
||||
<style name="FakeChatViewMessageBubble">
|
||||
<item name="android:paddingLeft">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingTop">12dp</item>
|
||||
<item name="android:paddingRight">@dimen/medium_spacing</item>
|
||||
<item name="android:paddingBottom">12dp</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:elevation">0dp</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="FakeChatViewMessageBubble.Incoming">
|
||||
<item name="android:background">@drawable/fake_chat_view_incoming_message_background</item>
|
||||
<item name="android:elevation">10dp</item>
|
||||
</style>
|
||||
|
||||
<style name="FakeChatViewMessageBubble.Outgoing">
|
||||
<item name="android:background">@drawable/fake_chat_view_outgoing_message_background</item>
|
||||
<item name="android:elevation">10dp</item>
|
||||
</style>
|
||||
<!-- Session -->
|
||||
|
||||
<style name="NoAnimation.Theme.BlackScreen" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="NoAnimation.Theme.AppCompat.Light.DarkActionBar" parent="@style/Theme.AppCompat.Light.DarkActionBar">
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DialogActivity" parent="Theme.AppCompat.Light">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
|
||||
<style name="AppCompatAlertDialogStyleLight" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/signal_primary_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="AppCompatAlertDialogStyleDark" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="colorAccent">@color/signal_primary</item>
|
||||
<item name="android:textColor">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="AppCompatDialogStyleLight" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorAccent">@color/signal_primary_dark</item>
|
||||
<item name="android:windowBackground">@drawable/dialog_background</item>
|
||||
</style>
|
||||
|
||||
<style name="AppCompatDialogStyleDark" parent="Theme.AppCompat.Dialog">
|
||||
<item name="colorAccent">@color/signal_primary</item>
|
||||
<item name="android:windowBackground">@drawable/dialog_background</item>
|
||||
<item name="android:textColor">@null</item>
|
||||
</style>
|
||||
|
||||
<!-- ActionBar styles -->
|
||||
<style name="TextSecure.DarkActionBar"
|
||||
parent="@style/Widget.AppCompat.ActionBar">
|
||||
<item name="background">@color/core_grey_90</item>
|
||||
<item name="android:popupTheme" tools:ignore="NewApi">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
<item name="elevation">1dp</item>
|
||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
<item name="titleTextStyle">@style/TextSecure.TitleTextStyle</item>
|
||||
<item name="subtitleTextStyle">@style/TextSecure.SubtitleTextStyle</item>
|
||||
<item name="android:textColorSecondary">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightActionBar"
|
||||
parent="@style/Widget.AppCompat.ActionBar">
|
||||
<item name="background">@color/textsecure_primary</item>
|
||||
<item name="elevation">1dp</item>
|
||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||
<item name="titleTextStyle">@style/TextSecure.TitleTextStyle</item>
|
||||
<item name="subtitleTextStyle">@style/TextSecure.SubtitleTextStyle</item>
|
||||
<item name="android:textColorPrimary">@color/white</item>
|
||||
<item name="android:textColorSecondary">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightActionBar.DarkText"
|
||||
parent="TextSecure.LightActionBar">
|
||||
<item name="android:textColorPrimary">@color/black</item>
|
||||
<item name="android:textColorSecondary">@color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.FlatLightActionBar"
|
||||
parent="@style/TextSecure.LightActionBar">
|
||||
<item name="elevation">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkActionBar.TabBar"
|
||||
parent="@style/Widget.AppCompat.ActionBar.TabBar">
|
||||
<item name="background">@color/gray95</item>
|
||||
<item name="android:background">@color/gray95</item>
|
||||
<item name="elevation">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightActionBar.TabBar"
|
||||
parent="@style/Widget.AppCompat.ActionBar.TabBar">
|
||||
<item name="android:background">@color/textsecure_primary</item>
|
||||
<item name="background">@color/textsecure_primary</item>
|
||||
<item name="android:textColorPrimary">@color/white</item>
|
||||
<item name="android:textColorSecondary">#BFffffff</item>
|
||||
<item name="elevation">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.TitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:textColorHint">#BFffffff</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.SubtitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
|
||||
<item name="android:textColor">#BFffffff</item>
|
||||
</style>
|
||||
|
||||
<style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent">
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
<style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title">
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Registration.Description" parent="@android:style/TextAppearance">
|
||||
<item name="android:textSize">16.0sp</item>
|
||||
<item name="android:typeface">sans</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:gravity">left</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:shadowColor">#ffffff</item>
|
||||
<item name="android:shadowDx">1.0</item>
|
||||
<item name="android:shadowDy">1.0</item>
|
||||
<item name="android:shadowRadius">0.0</item>
|
||||
<item name="android:lineSpacingMultiplier">1.25</item>
|
||||
</style>
|
||||
|
||||
<style name="Registration.Label" parent="@android:style/TextAppearance">
|
||||
<item name="android:textSize">12.0sp</item>
|
||||
<item name="android:typeface">sans</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:textColor">#ff808080</item>
|
||||
<item name="android:gravity">left</item>
|
||||
<item name="android:layout_gravity">left</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:shadowColor">#ffffff</item>
|
||||
<item name="android:shadowDx">1.0</item>
|
||||
<item name="android:shadowDy">1.0</item>
|
||||
<item name="android:shadowRadius">0.0</item>
|
||||
<item name="android:lineSpacingMultiplier">1.25</item>
|
||||
</style>
|
||||
|
||||
<style name="Registration.BigLabel" parent="@style/Registration.Label">
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Registration.Constant" parent="@android:style/TextAppearance">
|
||||
<item name="android:typeface">sans</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:textColor">#ff808080</item>
|
||||
<item name="android:shadowColor">#ffffff</item>
|
||||
<item name="android:shadowDx">1.0</item>
|
||||
<item name="android:shadowDy">1.0</item>
|
||||
<item name="android:shadowRadius">0.0</item>
|
||||
<item name="android:lineSpacingMultiplier">1.25</item>
|
||||
</style>
|
||||
|
||||
<!-- For Holo Light Dialog Activity Styling Emulation -->
|
||||
|
||||
<style name="Widget.ProgressBar.Horizontal" parent="@android:style/Widget.Holo.ProgressBar.Horizontal">
|
||||
</style>
|
||||
|
||||
<style name="MaterialButton">
|
||||
<item name="android:elevation" tools:ignore="NewApi">1dp</item>
|
||||
<item name="android:translationZ" tools:ignore="NewApi">1dp</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
</style>
|
||||
|
||||
<style name="InfoButton" parent="@style/MaterialButton">
|
||||
<item name="android:background">@drawable/info_round</item>
|
||||
</style>
|
||||
|
||||
<style name="ErrorButton" parent="@style/MaterialButton">
|
||||
<item name="android:background">@drawable/error_round</item>
|
||||
</style>
|
||||
|
||||
<style name="AttachmentTypeLabel">
|
||||
<item name="android:textColor">?android:textColorTertiary</item>
|
||||
<item name="android:textSize">@dimen/small_font_size</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.Primary" parent="Base.Widget.AppCompat.Button.Colored">
|
||||
<item name="colorAccent">@color/signal_primary</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.Borderless" parent="Base.Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textColor">@color/signal_primary</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.Borderless.Registration" parent="Base.Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textColor">@color/core_grey_60</item>
|
||||
</style>
|
||||
|
||||
<!-- RedPhone -->
|
||||
|
||||
<!-- Buttons in the main "button row" of the in-call onscreen touch UI. -->
|
||||
|
||||
<!-- "Compound button" variation of InCallButton.
|
||||
These buttons have the concept of two states: checked and unchecked.
|
||||
(This style is just like "InCallButton" except that we also
|
||||
clear out android:textOn and android:textOff, to avoid the default
|
||||
text label behavior of the ToggleButton class.) -->
|
||||
|
||||
<style name="WebRtcCallCompoundButton">
|
||||
<item name="android:layout_height">31dp</item>
|
||||
<item name="android:layout_width">31dp</item>
|
||||
<item name="android:textOn">@null</item>
|
||||
<item name="android:textOff">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="IdentityKey">
|
||||
<item name="android:fontFamily">monospace</item>
|
||||
<item name="android:typeface">monospace</item>
|
||||
<item name="android:textSize">17sp</item>
|
||||
<item name="android:clickable">false</item>
|
||||
<item name="android:focusable">false</item>
|
||||
</style>
|
||||
|
||||
<style name="BackupPassphrase">
|
||||
<item name="android:fontFamily">monospace</item>
|
||||
<item name="android:typeface">monospace</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:clickable">false</item>
|
||||
<item name="android:focusable">false</item>
|
||||
</style>
|
||||
|
||||
<style name="PreferenceThemeOverlay.Fix" parent="PreferenceThemeOverlay.v14.Material">
|
||||
</style>
|
||||
|
||||
<style name="Color1SwitchStyle">
|
||||
<item name="colorControlActivated">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="BottomSheetActionItem">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/action_item_height</item>
|
||||
<item name="android:textSize">@dimen/medium_font_size</item>
|
||||
<item name="android:drawablePadding">@dimen/drawable_padding</item>
|
||||
<item name="android:padding">@dimen/normal_padding</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:selectable">true</item>
|
||||
<item name="android:foreground">?attr/selectableItemBackground</item>
|
||||
</style>
|
||||
|
||||
<style name="StickerPopupAnimation" parent="@android:style/Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/fade_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/fade_out</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
51
libsession/src/main/res/values/text_styles.xml
Normal file
51
libsession/src/main/res/values/text_styles.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Signal.Text.Headline" parent="Base.TextAppearance.AppCompat.Headline">
|
||||
<item name="android:textSize">28sp</item>
|
||||
<item name="android:lineSpacingExtra">3sp</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:letterSpacing" tools:ignore="NewApi">0</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Headline.Registration" parent="Signal.Text.Headline">
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Body" parent="Base.TextAppearance.AppCompat.Body1">
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:lineSpacingExtra">4sp</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:letterSpacing" tools:ignore="NewApi">0.01</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Preview" parent="Base.TextAppearance.AppCompat.Body2">
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:lineSpacingExtra">3sp</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:letterSpacing" tools:ignore="NewApi">0.01</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Caption" parent="Base.TextAppearance.AppCompat.Caption">
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:lineSpacingExtra">2sp</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:letterSpacing" tools:ignore="NewApi">0.03</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Signal.Text.Caption.MessageSent">
|
||||
<item name="android:textColor">?conversation_item_sent_text_secondary_color</item>
|
||||
<item name="android:shadowRadius">0</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Caption.MessageReceived">
|
||||
<item name="android:textColor">?conversation_item_received_text_secondary_color</item>
|
||||
<item name="android:shadowRadius">0</item>
|
||||
<item name="android:alpha">0.7</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Caption.MessageImageOverlay">
|
||||
<item name="android:textColor">@color/core_white</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
294
libsession/src/main/res/values/themes.xml
Normal file
294
libsession/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,294 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Session -->
|
||||
|
||||
<!-- Due to historical reasons the base theme is dark and the light one
|
||||
is implemented using "notnight" type of resources. -->
|
||||
<style name="Base.Theme.Session" parent="@style/Theme.AppCompat.DayNight.DarkActionBar">
|
||||
<item name="colorPrimary">@color/action_bar_background</item>
|
||||
<item name="colorPrimaryDark">@color/action_bar_background</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="colorControlHighlight">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/text</item>
|
||||
<item name="android:textColorSecondary">?android:textColorPrimary</item>
|
||||
<item name="android:textColorTertiary">@color/unimportant</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:textColorHint">@color/gray27</item>
|
||||
<item name="android:windowBackground">@drawable/default_session_background</item>
|
||||
<item name="android:colorBackground">@color/default_background_start</item>
|
||||
<item name="android:navigationBarColor">@color/compose_view_background</item>
|
||||
|
||||
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||
<item name="actionBarWidgetTheme">@null</item>
|
||||
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.DayNight.ActionBar</item>
|
||||
<item name="actionBarStyle">@style/Widget.Session.ActionBar</item>
|
||||
|
||||
<item name="alertDialogTheme">@style/ThemeOverlay.Session.AlertDialog</item>
|
||||
<item name="bottomSheetDialogTheme">@style/Theme.Session.BottomSheet</item>
|
||||
<item name="preferenceTheme">@style/ThemeOverlay.Session.Settings</item>
|
||||
<item name="appBarLayoutStyle">@style/Widget.Session.AppBarLayout</item>
|
||||
<item name="actionBarTabBarStyle">@style/Widget.Session.TabBar</item>
|
||||
|
||||
<item name="statusBarBackground">@color/accent</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="actionModeCloseDrawable">@drawable/ic_baseline_clear_24</item>
|
||||
<item name="actionModeBackground">@color/compose_view_background</item>
|
||||
|
||||
<item name="dividerVertical">@color/separator</item>
|
||||
<item name="dividerHorizontal">?dividerVertical</item>
|
||||
|
||||
<item name="searchViewStyle">@style/Widget.Session.SearchView</item>
|
||||
|
||||
<!-- App specific attributes -->
|
||||
<item name="ic_visibility_on">@drawable/ic_baseline_visibility_24</item>
|
||||
<item name="ic_visibility_off">@drawable/ic_baseline_visibility_off_24</item>
|
||||
<item name="ic_arrow_forward">@drawable/ic_baseline_arrow_forward_24</item>
|
||||
|
||||
<item name="dialog_background_color">@color/dialog_background</item>
|
||||
|
||||
<item name="media_overview_toolbar_background">@color/transparent</item>
|
||||
<item name="media_overview_header_foreground">@color/text</item>
|
||||
<item name="media_keyboard_button_color">@color/core_grey_25</item>\
|
||||
|
||||
<item name="attachment_type_selector_background">?android:windowBackground</item>
|
||||
<item name="attachment_type_selector_hide_button_background">@color/gray50</item>
|
||||
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
|
||||
<item name="attachment_document_icon_large">@drawable/ic_document_large_dark</item>
|
||||
<item name="attachment_document_extension_text_color">#222</item>
|
||||
|
||||
<item name="home_gradient_start">#00000000</item>
|
||||
<item name="home_gradient_end">#FF000000</item>
|
||||
|
||||
<item name="message_received_background_color">#222325</item>
|
||||
<item name="message_sent_background_color">#3F4146</item>
|
||||
|
||||
<item name="menu_accept_icon">@drawable/ic_baseline_done_24</item>
|
||||
<item name="menu_trash_icon">@drawable/ic_baseline_delete_24</item>
|
||||
<item name="menu_block_icon">@drawable/ic_baseline_block_24</item>
|
||||
<item name="menu_forward_icon">@drawable/ic_baseline_forward_24</item>
|
||||
<item name="menu_save_icon">@drawable/ic_baseline_save_24</item>
|
||||
<item name="menu_photo_library_icon">@drawable/ic_baseline_photo_library_24</item>
|
||||
<item name="menu_delete_icon">@drawable/ic_baseline_delete_24</item>
|
||||
<item name="menu_copy_icon">@drawable/ic_baseline_file_copy_24</item>
|
||||
<item name="menu_reply_icon">@drawable/ic_baseline_reply_24</item>
|
||||
<item name="menu_selectall_icon">@drawable/ic_baseline_select_all_24</item>
|
||||
<item name="menu_split_icon">@drawable/ic_baseline_call_split_24</item>
|
||||
<item name="menu_popup_expand">@drawable/ic_baseline_launch_24</item>
|
||||
<item name="menu_info_icon">@drawable/ic_baseline_info_24</item>
|
||||
|
||||
<item name="conversation_emoji_toggle">@drawable/ic_emoji_filled_keyboard_24</item>
|
||||
<item name="conversation_sticker_toggle">@drawable/ic_sticker_filled_keyboard_24</item>
|
||||
<item name="conversation_keyboard_toggle">@drawable/ic_baseline_keyboard_24</item>
|
||||
<item name="conversation_input_background">@drawable/compose_background_dark</item>
|
||||
|
||||
<item name="quick_camera_icon">@drawable/ic_baseline_photo_camera_24</item>
|
||||
<item name="quick_mic_icon">@drawable/ic_baseline_mic_24</item>
|
||||
|
||||
<item name="conversation_item_audio_seek_bar_color_incoming">@color/accent</item>
|
||||
<item name="conversation_item_audio_seek_bar_color_outgoing">@color/accent</item>
|
||||
<item name="conversation_item_audio_seek_bar_background_color">@color/text</item>
|
||||
</style>
|
||||
|
||||
<!-- This should be the default theme for the application. -->
|
||||
<style name="Theme.Session.DayNight" parent="Base.Theme.Session">
|
||||
<!-- leave empty to allow overriding -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.Session.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Session.DayNight.FlatActionBar">
|
||||
<item name="actionBarStyle">@style/Widget.Session.ActionBar.Flat</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
This is a temporary theme that is used by any activity
|
||||
which doesn't have support for light theme
|
||||
(like some old Signal screens or third-party libs with white only icons)
|
||||
-->
|
||||
<!-- TODO Refactor this to use color resources -->
|
||||
<style name="Base.Theme.Session.ForceDark" parent="Theme.Session.DayNight">
|
||||
<item name="colorPrimary">#171717</item>
|
||||
<item name="android:textColorPrimary">#FFFFFF</item>
|
||||
<item name="android:textColorSecondary">#DFFFFFFF</item>
|
||||
<item name="android:textColorTertiary">#90FFFFFF</item>
|
||||
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||
<item name="android:colorBackground">#121212</item>
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:navigationBarColor">?android:colorBackground</item>
|
||||
<item name="android:statusBarColor">@color/transparent</item>
|
||||
|
||||
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
<item name="actionBarWidgetTheme">@null</item>
|
||||
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
|
||||
<item name="actionBarStyle">@style/Widget.AppCompat.ActionBar</item>
|
||||
|
||||
<item name="conversation_emoji_toggle">@drawable/ic_emoji_filled_keyboard_24</item>
|
||||
<item name="conversation_sticker_toggle">@drawable/ic_sticker_filled_keyboard_24</item>
|
||||
<item name="conversation_keyboard_toggle">@drawable/ic_baseline_keyboard_24</item>
|
||||
<item name="conversation_input_background">@drawable/compose_background_dark</item>
|
||||
</style>
|
||||
<style name="Theme.Session.ForceDark" parent="Base.Theme.Session.ForceDark">
|
||||
<!-- leave empty to allow overriding -->
|
||||
</style>
|
||||
<style name="Theme.Session.ForceDark.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Session.BottomSheet" parent="@style/Theme.AppCompat.DayNight.Dialog">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowAnimationStyle">@style/Animation.MaterialComponents.BottomSheetDialog</item>
|
||||
<item name="bottomSheetStyle">@style/Widget.Session.BottomSheetDialog</item>
|
||||
</style>
|
||||
|
||||
<!-- Session -->
|
||||
|
||||
|
||||
|
||||
<!-- Original Signal dark theme -->
|
||||
<style name="Base.Theme.TextSecure" parent="@style/Theme.Session.DayNight">
|
||||
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
|
||||
<item name="colorPrimary">@color/action_bar_background</item>
|
||||
<item name="colorPrimaryDark">@color/action_bar_background</item>
|
||||
|
||||
<item name="media_overview_toolbar_background">@color/transparent</item>
|
||||
<item name="media_overview_header_foreground">@color/text</item>
|
||||
|
||||
<item name="theme_type">dark</item>
|
||||
<item name="android:navigationBarColor">@color/compose_view_background</item>
|
||||
|
||||
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
|
||||
<item name="attachment_document_icon_large">@drawable/ic_document_large_dark</item>
|
||||
|
||||
<item name="conversation_list_item_background">@drawable/conversation_list_item_background_dark</item>
|
||||
<item name="conversation_list_item_contact_color">#ffdddddd</item>
|
||||
<item name="conversation_list_item_subject_color">#ffdddddd</item>
|
||||
<item name="conversation_list_item_delivery_icon_color">@color/core_grey_25</item>
|
||||
<item name="conversation_list_item_date_color">#ffdddddd</item>
|
||||
<item name="conversation_list_item_unread_color">@color/core_white</item>
|
||||
<item name="conversation_list_item_unread_background">@drawable/unread_count_background_dark</item>
|
||||
<item name="conversation_list_item_divider">@drawable/conversation_list_divider_shape_dark</item>
|
||||
<item name="conversation_list_toolbar_background">@color/action_bar_background</item>
|
||||
<item name="conversation_list_typing_tint">@color/core_white</item>
|
||||
|
||||
<item name="conversation_group_member_name">#99ffffff</item>
|
||||
|
||||
<item name="conversation_item_bubble_background">?message_sent_background_color</item>
|
||||
<item name="conversation_item_sent_text_primary_color">@color/core_grey_05</item>
|
||||
<item name="conversation_item_sent_text_secondary_color">@color/core_grey_25</item>
|
||||
<item name="conversation_item_sent_icon_color">@color/core_grey_25</item>
|
||||
<item name="conversation_item_sent_text_indicator_tab_color">#99ffffff</item>
|
||||
<item name="conversation_item_received_text_primary_color">@color/text</item>
|
||||
<item name="conversation_item_received_text_secondary_color">@color/text</item>
|
||||
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
|
||||
<item name="conversation_item_sticky_date_background">@drawable/sticky_date_header_background_dark</item>
|
||||
<item name="conversation_item_sticky_date_text_color">@color/core_grey_45</item>
|
||||
<item name="conversation_item_image_outline_color">@color/transparent_white_30</item>
|
||||
|
||||
<item name="verification_background">@color/core_grey_95</item>
|
||||
|
||||
<item name="dialog_info_icon">@drawable/ic_info_outline_dark</item>
|
||||
<item name="dialog_alert_icon">@drawable/ic_warning_dark</item>
|
||||
|
||||
<item name="device_link_item_card_background">@color/device_link_item_background_dark</item>
|
||||
|
||||
<item name="fab_color">@color/textsecure_primary_dark</item>
|
||||
<item name="lower_right_divet">@drawable/divet_lower_right_light</item>
|
||||
|
||||
<item name="conversation_background">@color/loki_darkest_gray</item>
|
||||
<item name="conversation_editor_background">#22ffffff</item>
|
||||
<item name="conversation_editor_text_color">#ffeeeeee</item>
|
||||
<item name="conversation_input_inline_attach_icon_tint">@color/core_grey_05</item>
|
||||
<item name="conversation_transport_sms_indicator">@drawable/ic_arrow_up_circle_24</item>
|
||||
<item name="conversation_transport_push_indicator">@drawable/ic_arrow_up_circle_24</item>
|
||||
<item name="conversation_transport_popup_background">@color/black</item>
|
||||
<item name="conversation_attach_camera">@drawable/ic_photo_camera_dark</item>
|
||||
<item name="conversation_attach_image">@drawable/ic_image_dark</item>
|
||||
<item name="conversation_attach_video">@drawable/ic_movie_creation_dark</item>
|
||||
<item name="conversation_attach_sound">@drawable/ic_volume_up_dark</item>
|
||||
<item name="conversation_attach_contact_info">@drawable/ic_account_box_dark</item>
|
||||
<item name="conversation_attach">@drawable/ic_attach_white_24dp</item>
|
||||
<item name="conversation_number_picker_text_color_normal">@color/gray13</item>
|
||||
<item name="conversation_number_picker_text_color_selected">@color/white</item>
|
||||
<item name="conversation_sticker_footer_text_color">@color/core_grey_25</item>
|
||||
<item name="conversation_sticker_footer_icon_color">@color/core_grey_25</item>
|
||||
<item name="conversation_sticker_author_color">@color/core_grey_05</item>
|
||||
|
||||
<item name="emoji_tab_strip_background">@color/compose_view_background</item>
|
||||
<item name="emoji_tab_indicator">@color/accent</item>
|
||||
<item name="emoji_tab_underline">@color/gray78</item>
|
||||
<item name="emoji_tab_seperator">@color/gray70</item>
|
||||
<item name="emoji_drawer_background">@color/compose_text_view_background</item>
|
||||
<item name="emoji_text_color">@color/white</item>
|
||||
|
||||
<item name="emoji_category_recent">@drawable/ic_recent_dark_20</item>
|
||||
<item name="emoji_category_people">@drawable/ic_emoji_people_dark_20</item>
|
||||
<item name="emoji_category_nature">@drawable/ic_emoji_animal_dark_20</item>
|
||||
<item name="emoji_category_foods">@drawable/ic_emoji_food_dark_20</item>
|
||||
<item name="emoji_category_activity">@drawable/ic_emoji_activity_dark_20</item>
|
||||
<item name="emoji_category_places">@drawable/ic_emoji_travel_dark_20</item>
|
||||
<item name="emoji_category_objects">@drawable/ic_emoji_object_dark_20</item>
|
||||
<item name="emoji_category_symbol">@drawable/ic_emoji_symbol_dark_20</item>
|
||||
<item name="emoji_category_flags">@drawable/ic_emoji_flag_dark_20</item>
|
||||
<item name="emoji_category_emoticons">@drawable/ic_emoji_emoticon_dark_20</item>
|
||||
<item name="emoji_variation_selector_background">@drawable/emoji_variation_selector_background_dark</item>
|
||||
|
||||
<item name="linkpreview_background_color">@color/black</item>
|
||||
<item name="linkpreview_primary_text_color">@color/text</item>
|
||||
<item name="linkpreview_secondary_text_color">@color/text</item>
|
||||
<item name="linkpreview_divider_color">@color/transparent</item>
|
||||
|
||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
|
||||
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
|
||||
|
||||
<item name="sticker_management_icon">@drawable/sticker_button_dark</item>
|
||||
<item name="sticker_management_divider_color">@color/core_grey_75</item>
|
||||
<item name="sticker_management_empty_background_color">@color/core_grey_85</item>
|
||||
<item name="sticker_management_action_button_color">@color/core_grey_25</item>
|
||||
<item name="sticker_popup_background">@color/transparent_black_90</item>
|
||||
<item name="sticker_preview_toolbar_background">@color/core_grey_95</item>
|
||||
<item name="sticker_preview_status_bar_color">@color/core_grey_85</item>
|
||||
<item name="sticker_view_missing_background">@drawable/sticker_missing_background_dark</item>
|
||||
|
||||
<item name="tooltip_default_color">@color/core_grey_75</item>
|
||||
|
||||
<item name="pref_icon_tint">#FFFFFF</item>
|
||||
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
|
||||
<item name="shared_contact_details_header_background">@color/grey_800</item>
|
||||
<item name="shared_contact_details_titlebar">@color/grey_900</item>
|
||||
<item name="shared_contact_item_button_color">@color/core_grey_85</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.TextSecure.DayNight" parent="Base.Theme.TextSecure">
|
||||
<!-- leave empty to allow overriding -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.TextSecure.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.TextSecure.Dialog.Rationale" parent="Theme.AppCompat.DayNight.Dialog.Alert">
|
||||
<item name="android:windowBackground">@drawable/default_dialog_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.TextSecure.Dialog.MediaSendProgress" parent="@android:style/Theme.Dialog">
|
||||
<item name="android:background">@drawable/default_dialog_background</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
5
libsession/src/main/res/values/values.xml
Normal file
5
libsession/src/main/res/values/values.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="enable_alarm_manager">true</bool>
|
||||
<bool name="enable_job_service">false</bool>
|
||||
</resources>
|
20
libsession/src/main/res/values/vector_paths.xml
Normal file
20
libsession/src/main/res/values/vector_paths.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Play Icon -->
|
||||
<string name="play_icon_upper_path_data">M 44 32 L 44 64 L 100 64 L 100 64 Z</string>
|
||||
<string name="play_icon_bottom_path_data">M 44 96 L 44 64 L 100 64 L 100 64 Z</string>
|
||||
|
||||
<!-- Pause Icon -->
|
||||
<string name="pause_icon_upper_path_data">M 32 40 L 32 56 L 96 56 L 96 40 Z</string>
|
||||
<string name="pause_icon_bottom_path_data">M 32 88 L 32 72 L 96 72 L 96 88 Z</string>
|
||||
|
||||
<!-- Groups -->
|
||||
<string name="play_icon_group_top">upperpart</string>
|
||||
<string name="play_icon_group_bottom">bottompart</string>
|
||||
<string name="play_icon_group_parts">parts</string>
|
||||
|
||||
<!-- Path Name -->
|
||||
<string name="play_icon_top_path_name">upper</string>
|
||||
<string name="play_icon_bottom_path_name">bottom</string>
|
||||
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user