move Recipient to libsession

This commit is contained in:
Ryan ZHAO 2021-01-08 11:10:51 +11:00
parent 2dcbcee66c
commit 91ef23081b
66 changed files with 8186 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
package org.session.libsession.messaging.threads.recipients;
public interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,5 @@
package org.session.libsession.utilities
import org.session.libsession.messaging.threads.recipients.Recipient
data class ProfilePictureModifiedEvent(val recipient: Recipient)

View File

@ -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)
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -0,0 +1,8 @@
package org.session.libsession.utilities.dynamiclanguage
import java.util.Locale
interface LocaleParserHelperProtocol {
fun appSupportsTheExactLocale(locale: Locale?): Boolean
fun findBestSystemLocale(): Locale
}

View File

@ -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;
}
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View 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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#333132</color>
</resources>

View 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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="play_button_animation_duration">300</integer>
</resources>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

View 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>

View 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>