New app theming (#913)

* feat: start new app theming feature

* feat: add some theming colours

* refactor: start refactoring themes and colours to use dynamic attributes

* feat: adding more colours and switching over default colours to be theme based instead of hard-coded or day/night specific

* refactor: take a look at ocean light and logo colour

* feat: global search colours for light and dark ocean

* feat: more styling

* feat: adding themes to conversation activity and refactoring the base theme to apply over the top of the activity's theme so it retains noActionBar etc

* feat: add dynamic accent color

* docs: add todo for changing how accent colour is applied

* feat: update new theming to use override primary style so that the regular colorAccent attribute can be used in existing layouts

* feat: coordinating styles across layouts, fixing up pinned icons and naming for conversation list items

* refactor: re-styling layouts to match new themes and attributes. Need to figure out action mode close button

* refactor: remove @color/text and replace with ?android:textColorPrimary to override in themes

* refactor: add context theme wrapper to bottom sheet dialog that references accent color

* fix: input bar bug fix and preference activity themes

* refactor: new settings menu options

* fix: crash for PNModeActivity.kt

refactor: move ordering in seed dialog to match designs, copy changes to match new settings menu

* feat: add new appearance settings activity

* refactor: title and VM changes

* fix: correct override

* feat: add theme appearance screen UI features and start VM implementation. re-add legacy theme utils to get default for migration

* fix: compile errors and missing themes from emoji features

* refactor: remove background shape alteration and old bottom sheet styles, re-add the theme mode attr

* feat: appearance screen wired up, just need to refresh theme

* feat: add theme state recreation and fix match system settings option

* refactor: add bottom margin

* feat: explore custom preference category

* feat: add the customized session theme for CorrectedPreferenceFragment

* feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment

* refactor: change drawable style and remove explicit dividers

* refactor: remove divider in CorrectedPreferenceFragment

* feat: add theme state check on resume, might be jarring currently

* feat: add preference divider elements for settings menu

* refactor: settings menu redesigns

* refactor: change led preference to integer and refactor TextSecurePreferences.kt

* feat: add scroll parcel to save/restore hierarchy on restart with appearance changes

* feat: add the conversations blocked contacts and refactor preference order and copy

* feat: add blocked contacts activity, basic layout and vm

* feat: add unblock DB functions and storage protocol, start working on the DB query state flow, might have to just implement recipient on modified listener

* feat: add blocked contacts and notif recipient listeners

* feat: add recipient db reader

* feat: add blocked contact interactions and fix a theming crash for notifications

* feat: introduce better equals and hashcode implementations to recipient, replace home diff util content check with hashcode-based comparison

* feat: add settings menu vectors

* fix: preview compile error

* refactor: migrating settings menu to new designs

* feat: help menu

* refactor: simplify link opening

* refactor: remove space

* feat: refactor preferences and start theming for light mode options

* refactor: fixing dark and light modes with dialogs

* refactor: popup dialogs use proper themes now

* refactor: alert dialogs and media edit fragments use attribute references

* refactor: use input bar button attribute instead color control normal in vector tint

* refactor: transparency, dialog fixes, notification fix

* refactor: attrs and styles for buttons

* fix: use prominent button color on the outline button's border

* fix: fix the trash

* refactor: remove the appearance

* refactor: avatar placeholder generation, chips and element border styles

* refactor: use colors instead of style references

* refactor: theming changes to match designs and feedback

* refactor: the titles are bold and the categories are tertiary coloured now

* fix: appearance settings match preferences, search bottom bar uses themed attributes

* refactor: increase setting button height

* Update clear all data dialog

* Update seed dialog

* refactor: more qa feedback changes

* feat: add new TLs and fa-rIR TLs

* Update notification content dialog

* Fix message requests clear all button text color

* feat: re-add screenshot observer

* refactor: make send tint accent color

* feat: add unread background differences

* fix: change unread count indicator

* build: upgrade build numbers

* Fix message requests popupmenu background color

* fix: crash from attr reference in color attribute

* build: upgrade build number

* fix: message bubbles, thumbnail backgrounds, search bar visibility with input bar, attachment buttons

* fix: tertiary text for keyboard page search view

* fix: emoji overflow colour differences

* fix: reaction pill dialog background is now correct colour

* Add style to reactions tab layout

* fix: appearance activity reverting primary color at correct time

* fix: show call privacy warning every time instead of just once

* fix: gradient background(?) and audio autoplay disable

* fix: crash in all media containing documents

* fix: reaction dialog heading fixes

* Add style to reactions tab layout

* fix: remove gradient backgrounds

* fix: adding new reaction normal text attribute to try correct the tab layout

* fix: ocean dark unread/read colours

* build; update build number

* build: update build number

Co-authored-by: charles <charles@oxen.io>
This commit is contained in:
Harris 2022-10-12 17:05:55 +11:00 committed by GitHub
parent 92075aed32
commit 7a773016da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
378 changed files with 4305 additions and 3062 deletions

View File

@ -27,7 +27,7 @@ configurations.all {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android:flexbox:2.0.1'
@ -157,8 +157,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 303
def canonicalVersionName = "1.15.4"
def canonicalVersionCode = 307
def canonicalVersionName = "1.16.0"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -140,6 +140,12 @@
android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
<activity
android:name="org.thoughtcrime.securesms.preferences.BlockedContactsActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.Session.DayNight.FlatActionBar"
android:label="@string/blocked_contacts_title"
/>
<activity
android:name="org.thoughtcrime.securesms.groups.EditClosedGroupActivity"
android:label="@string/activity_edit_closed_group_title"
@ -160,6 +166,12 @@
<activity
android:name="org.thoughtcrime.securesms.preferences.ChatSettingsActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.preferences.HelpSettingsActivity"
android:label="@string/activity_help_settings_title"
android:screenOrientation="portrait" />
<activity android:name="org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity"
android:screenOrientation="portrait"/>
<activity
android:name="org.thoughtcrime.securesms.ShareActivity"
@ -216,12 +228,12 @@
<activity
android:name="org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.TextSecure.DayNight">
android:theme="@style/Theme.Session.DayNight">
</activity>
<activity
android:name="org.thoughtcrime.securesms.longmessage.LongMessageActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.TextSecure.DayNight" />
android:theme="@style/Theme.Session.DayNight" />
<activity
android:name="org.thoughtcrime.securesms.DatabaseUpgradeActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -235,14 +247,14 @@
<activity
android:name="org.thoughtcrime.securesms.giph.ui.GiphyActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
android:theme="@style/Theme.Session.DayNight.NoActionBar"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="org.thoughtcrime.securesms.mediasend.MediaSendActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:screenOrientation="portrait"
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
android:theme="@style/Theme.Session.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="org.thoughtcrime.securesms.MediaPreviewActivity"
@ -381,6 +393,10 @@
android:name="org.thoughtcrime.securesms.database.DatabaseContentProviders$StickerPack"
android:authorities="network.loki.securesms.database.stickerpack"
android:exported="false" />
<provider
android:name="org.thoughtcrime.securesms.database.DatabaseContentProviders$Recipient"
android:authorities="network.loki.securesms.database.recipient"
android:exported="false" />
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver">
<intent-filter>

View File

@ -85,7 +85,6 @@ import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.util.Broadcaster;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
import org.webrtc.PeerConnectionFactory;
@ -165,6 +164,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
return (ApplicationContext) context.getApplicationContext();
}
public TextSecurePreferences getPrefs() {
return textSecurePreferences;
}
public DatabaseComponent getDatabaseComponent() {
return EntryPoints.get(getApplicationContext(), DatabaseComponent.class);
}
@ -220,7 +223,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
if (userPublicKey != null) {
registerForFCMIfNeeded(false);
}
UiModeUtilities.setupUiModeToUserSelected(this);
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();

View File

@ -1,44 +1,103 @@
package org.thoughtcrime.securesms;
import static org.session.libsession.utilities.TextSecurePreferences.SELECTED_ACCENT_COLOR;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
import org.thoughtcrime.securesms.util.ThemeState;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import network.loki.messenger.R;
public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
private ThemeState currentThemeState;
private TextSecurePreferences getPreferences() {
ApplicationContext appContext = (ApplicationContext) getApplicationContext();
return appContext.textSecurePreferences;
}
@StyleRes
public int getDesiredTheme() {
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
int userSelectedTheme = themeState.getTheme();
if (themeState.getFollowSystem()) {
// do light or dark based on the selected theme
boolean isDayUi = UiModeUtilities.isDayUiMode(this);
if (userSelectedTheme == R.style.Ocean_Dark || userSelectedTheme == R.style.Ocean_Light) {
return isDayUi ? R.style.Ocean_Light : R.style.Ocean_Dark;
} else {
return isDayUi ? R.style.Classic_Light : R.style.Classic_Dark;
}
} else {
return userSelectedTheme;
}
}
@StyleRes @Nullable
public Integer getAccentTheme() {
if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null;
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
return themeState.getAccentStyle();
}
@Override
public Resources.Theme getTheme() {
// New themes
Resources.Theme modifiedTheme = super.getTheme();
modifiedTheme.applyStyle(getDesiredTheme(), true);
Integer accentTheme = getAccentTheme();
if (accentTheme != null) {
modifiedTheme.applyStyle(accentTheme, true);
}
currentThemeState = ActivityUtilitiesKt.themeState(getPreferences());
return modifiedTheme;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
initializeScreenshotSecurity();
initializeScreenshotSecurity(true);
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
String name = getResources().getString(R.string.app_name);
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
int color = getResources().getColor(R.color.app_icon_background);
setTaskDescription(new ActivityManager.TaskDescription(name, icon, color));
if (!currentThemeState.equals(ActivityUtilitiesKt.themeState(getPreferences()))) {
recreate();
}
}
@Override
protected void onPause() {
super.onPause();
initializeScreenshotSecurity(false);
}
@Override
@ -49,11 +108,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
return true;
}
private void initializeScreenshotSecurity() {
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
private void initializeScreenshotSecurity(boolean isResume) {
if (!isResume) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
}

View File

@ -0,0 +1,78 @@
package org.thoughtcrime.securesms.attachments
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.provider.MediaStore
import androidx.annotation.RequiresApi
class ScreenshotObserver(private val context: Context, handler: Handler, private val screenshotTriggered: ()->Unit): ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
uri ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
queryRelativeDataColumn(uri)
} else {
queryDataColumn(uri)
}
}
private val cache = mutableSetOf<Int>()
private fun queryDataColumn(uri: Uri) {
val projection = arrayOf(
MediaStore.Images.Media.DATA
)
context.contentResolver.query(
uri,
projection,
null,
null,
null
)?.use { cursor ->
val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
while (cursor.moveToNext()) {
val path = cursor.getString(dataColumn)
if (path.contains("screenshot", true)) {
if (cache.add(uri.hashCode())) {
screenshotTriggered()
}
}
}
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun queryRelativeDataColumn(uri: Uri) {
val projection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.RELATIVE_PATH
)
context.contentResolver.query(
uri,
projection,
null,
null,
null
)?.use { cursor ->
val relativePathColumn =
cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
val displayNameColumn =
cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
while (cursor.moveToNext()) {
val name = cursor.getString(displayNameColumn)
val relativePath = cursor.getString(relativePathColumn)
if (name.contains("screenshot", true) or
relativePath.contains("screenshot", true)) {
if (cache.add(uri.hashCode())) {
screenshotTriggered()
}
}
}
}
}
}

View File

@ -107,7 +107,7 @@ class ProfilePictureView @JvmOverloads constructor(
if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return
val signalProfilePicture = recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
val placeholder = PlaceholderAvatarPhoto(publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.clear(imageView)
glide.load(signalProfilePicture)

View File

@ -1,304 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.session.libsession.messaging.contacts.Contact;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.database.SessionContactDatabase;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import java.util.List;
import network.loki.messenger.R;
public class QuoteView extends FrameLayout implements RecipientModifiedListener {
private static final String TAG = QuoteView.class.getSimpleName();
private static final int MESSAGE_TYPE_PREVIEW = 0;
private static final int MESSAGE_TYPE_OUTGOING = 1;
private static final int MESSAGE_TYPE_INCOMING = 2;
private ViewGroup mainView;
private ViewGroup footerView;
private TextView authorView;
private TextView bodyView;
private ImageView quoteBarView;
private ImageView thumbnailView;
private View attachmentVideoOverlayView;
private ViewGroup attachmentContainerView;
private TextView attachmentNameView;
private ImageView dismissView;
private long id;
private Recipient author;
private String body;
private Recipient conversationRecipient;
private TextView mediaDescriptionText;
private TextView missingLinkText;
private SlideDeck attachments;
private int messageType;
private int largeCornerRadius;
private int smallCornerRadius;
private CornerMask cornerMask;
public QuoteView(Context context) {
super(context);
initialize(null);
}
public QuoteView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(attrs);
}
private void initialize(@Nullable AttributeSet attrs) {
inflate(getContext(), R.layout.quote_view, this);
this.mainView = findViewById(R.id.quote_main);
this.footerView = findViewById(R.id.quote_missing_footer);
this.authorView = findViewById(R.id.quote_author);
this.bodyView = findViewById(R.id.quote_text);
this.quoteBarView = findViewById(R.id.quote_bar);
this.thumbnailView = findViewById(R.id.quote_thumbnail);
this.attachmentVideoOverlayView = findViewById(R.id.quote_video_overlay);
this.attachmentContainerView = findViewById(R.id.quote_attachment_container);
this.attachmentNameView = findViewById(R.id.quote_attachment_name);
this.dismissView = findViewById(R.id.quote_dismiss);
this.mediaDescriptionText = findViewById(R.id.media_type);
this.missingLinkText = findViewById(R.id.quote_missing_text);
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
cornerMask = new CornerMask(this);
cornerMask.setRadii(largeCornerRadius, largeCornerRadius, smallCornerRadius, smallCornerRadius);
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
int primaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorPrimary, Color.BLACK);
int secondaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorSecondary, Color.BLACK);
messageType = typedArray.getInt(R.styleable.QuoteView_message_type, 0);
typedArray.recycle();
dismissView.setVisibility(messageType == MESSAGE_TYPE_PREVIEW ? VISIBLE : GONE);
authorView.setTextColor(primaryColor);
bodyView.setTextColor(primaryColor);
attachmentNameView.setTextColor(primaryColor);
mediaDescriptionText.setTextColor(secondaryColor);
missingLinkText.setTextColor(primaryColor);
if (messageType == MESSAGE_TYPE_PREVIEW) {
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
cornerMask.setTopLeftRadius(radius);
cornerMask.setTopRightRadius(radius);
}
}
dismissView.setOnClickListener(view -> setVisibility(GONE));
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
cornerMask.mask(canvas);
}
public void setQuote(GlideRequests glideRequests,
long id,
@NonNull Recipient author,
@Nullable String body,
boolean originalMissing,
@NonNull SlideDeck attachments,
@NonNull Recipient conversationRecipient)
{
if (this.author != null) this.author.removeListener(this);
this.id = id;
this.author = author;
this.body = body;
this.attachments = attachments;
this.conversationRecipient = conversationRecipient;
author.addListener(this);
setQuoteAuthor(author);
setQuoteText(body, attachments);
setQuoteAttachment(glideRequests, attachments);
setQuoteMissingFooter(originalMissing);
}
public void setTopCornerSizes(boolean topLeftLarge, boolean topRightLarge) {
cornerMask.setTopLeftRadius(topLeftLarge ? largeCornerRadius : smallCornerRadius);
cornerMask.setTopRightRadius(topRightLarge ? largeCornerRadius : smallCornerRadius);
}
public void dismiss() {
if (this.author != null) this.author.removeListener(this);
this.id = 0;
this.author = null;
this.body = null;
setVisibility(GONE);
}
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(() -> {
if (recipient == author) {
setQuoteAuthor(recipient);
}
});
}
private void setQuoteAuthor(@NonNull Recipient author) {
boolean outgoing = messageType != MESSAGE_TYPE_INCOMING;
boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress().serialize());
String quoteeDisplayName;
String senderHexEncodedPublicKey = author.getAddress().serialize();
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
} else {
SessionContactDatabase contactDB = DatabaseComponent.get(getContext()).sessionContactDatabase();
Contact contact = contactDB.getContactWithSessionID(senderHexEncodedPublicKey);
if (contact != null) {
Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR;
quoteeDisplayName = contact.displayName(context);
} else {
quoteeDisplayName = senderHexEncodedPublicKey;
}
}
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName);
// We use the raw color resource because Android 4.x was struggling with tints here
int colorID = UiModeUtilities.isDayUiMode(getContext()) ? R.color.black : R.color.accent;
quoteBarView.setImageResource(colorID);
mainView.setBackgroundColor(ThemeUtil.getThemedColor(getContext(),
outgoing ? R.attr.message_received_background_color : R.attr.message_sent_background_color));
}
private void setQuoteText(@Nullable String body, @NonNull SlideDeck attachments) {
if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) {
bodyView.setVisibility(VISIBLE);
bodyView.setText(body == null ? "" : body);
mediaDescriptionText.setVisibility(GONE);
return;
}
bodyView.setVisibility(GONE);
mediaDescriptionText.setVisibility(VISIBLE);
List<Slide> audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList();
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
List<Slide> imageSlides = Stream.of(attachments.getSlides()).filter(Slide::hasImage).limit(1).toList();
List<Slide> videoSlides = Stream.of(attachments.getSlides()).filter(Slide::hasVideo).limit(1).toList();
// Given that most types have images, we specifically check images last
if (!audioSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_audio);
} else if (!documentSlides.isEmpty()) {
mediaDescriptionText.setVisibility(GONE);
} else if (!videoSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_video);
} else if (!imageSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_photo);
}
}
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
attachmentVideoOverlayView.setVisibility(GONE);
if (!imageVideoSlides.isEmpty() && imageVideoSlides.get(0).getThumbnailUri() != null) {
thumbnailView.setVisibility(VISIBLE);
attachmentContainerView.setVisibility(GONE);
dismissView.setBackgroundResource(R.drawable.dismiss_background);
if (imageVideoSlides.get(0).hasVideo()) {
attachmentVideoOverlayView.setVisibility(VISIBLE);
}
glideRequests.load(new DecryptableUri(imageVideoSlides.get(0).getThumbnailUri()))
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(thumbnailView);
} else if (!documentSlides.isEmpty()){
thumbnailView.setVisibility(GONE);
attachmentContainerView.setVisibility(VISIBLE);
attachmentNameView.setText(documentSlides.get(0).getFileName().or(""));
} else {
thumbnailView.setVisibility(GONE);
attachmentContainerView.setVisibility(GONE);
dismissView.setBackgroundDrawable(null);
}
if (ThemeUtil.isDarkTheme(getContext())) {
dismissView.setBackgroundResource(R.drawable.circle_alpha);
}
}
private void setQuoteMissingFooter(boolean missing) {
footerView.setVisibility(missing ? VISIBLE : GONE);
footerView.setBackgroundColor(getResources().getColor(R.color.quote_not_found_background));
}
public long getQuoteId() {
return id;
}
public Recipient getAuthor() {
return author;
}
public String getBody() {
return body;
}
public List<Attachment> getAttachments() {
return attachments.asAttachments();
}
}

View File

@ -1,11 +1,10 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import android.util.AttributeSet;
import network.loki.messenger.R;
@ -18,7 +17,6 @@ public class SwitchPreferenceCompat extends CheckBoxPreference {
setLayoutRes();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutRes();

View File

@ -68,18 +68,22 @@ class UserView : LinearLayout {
}
ActionIndicator.Tick -> {
binding.actionIndicatorImageView.visibility = View.VISIBLE
binding.actionIndicatorImageView.setImageResource(
if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle
)
if (isSelected) {
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
} else {
binding.actionIndicatorImageView.setImageDrawable(null)
}
}
}
}
fun toggleCheckbox(isSelected: Boolean = false) {
binding.actionIndicatorImageView.visibility = View.VISIBLE
binding.actionIndicatorImageView.setImageResource(
if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle
)
if (isSelected) {
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
} else {
binding.actionIndicatorImageView.setImageDrawable(null)
}
}
fun unbind() {

View File

@ -41,7 +41,7 @@ class NewConversationFragment : BottomSheetDialogFragment(), NewConversationDele
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(requireContext(), theme)
val dialog = BottomSheetDialog(requireContext(), R.style.Theme_Session_BottomSheet)
dialog.setOnShowListener {
val bottomSheetDialog = it as BottomSheetDialog
val parentLayout =

View File

@ -4,8 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.FragmentNewConversationHomeBinding
@ -57,5 +60,11 @@ class NewConversationHomeFragment : Fragment() {
contactGroups.remove(unknownSectionTitle)?.let { contactGroups.put(unknownSectionTitle, it) }
adapter.items = contactGroups.flatMap { entry -> listOf(ContactListItem.Header(entry.key)) + entry.value }
binding.contactsRecyclerView.adapter = adapter
val divider = ContextCompat.getDrawable(requireActivity(), R.drawable.conversation_menu_divider)!!.let {
DividerItemDecoration(requireActivity(), RecyclerView.VERTICAL).apply {
setDrawable(it)
}
}
binding.contactsRecyclerView.addItemDecoration(divider)
}
}

View File

@ -18,6 +18,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Pair
import android.util.TypedValue
@ -85,6 +86,7 @@ import org.session.libsignal.utilities.hexEncodedPrivateKey
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.ExpirationDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.attachments.ScreenshotObserver
import org.thoughtcrime.securesms.audio.AudioRecorder
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
@ -191,6 +193,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
@Inject lateinit var reactionDb: ReactionDatabase
@Inject lateinit var viewModelFactory: ConversationViewModel.AssistedFactory
private val screenshotObserver by lazy {
ScreenshotObserver(this, Handler(Looper.getMainLooper())) {
// post screenshot message
sendScreenshotNotification()
}
}
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val linkPreviewViewModel: LinkPreviewViewModel by lazy {
ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository()))
@ -380,11 +389,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId)
val recipient = viewModel.recipient ?: return
threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient)
contentResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
screenshotObserver
)
}
override fun onPause() {
super.onPause()
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1)
contentResolver.unregisterContentObserver(screenshotObserver)
}
override fun getSystemService(name: String): Any? {
@ -923,10 +938,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} else if (recipient.isGroupRecipient) {
viewModel.openGroup?.let { openGroup ->
val userCount = lokiApiDb.getUserCount(openGroup.room, openGroup.server) ?: 0
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_member_count, userCount)
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_active_member_count, userCount)
} ?: run {
actionBarBinding.conversationSubtitleView.isVisible = false
val userCount = groupDb.getGroupMemberAddresses(recipient.address.toGroupString(), true).size
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_member_count, userCount)
}
viewModel
} else {
actionBarBinding.conversationSubtitleView.isVisible = false
}
@ -1317,6 +1334,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
override fun playVoiceMessageAtIndexIfPossible(indexInAdapter: Int) {
if (!textSecurePreferences.autoplayAudioMessages()) return
if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return }
val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView
@ -1770,6 +1789,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
endActionMode()
}
private fun sendScreenshotNotification() {
val recipient = viewModel.recipient ?: return
if (recipient.isGroupRecipient) return
val kind = DataExtractionNotification.Kind.Screenshot()
val message = DataExtractionNotification(kind)
MessageSender.send(message, recipient.address)
}
private fun sendMediaSavedNotification() {
val recipient = viewModel.recipient ?: return
if (recipient.isGroupRecipient) { return }
@ -1819,7 +1846,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
searchViewModel.onSearchOpened()
binding?.searchBottomBar?.visibility = View.VISIBLE
binding?.searchBottomBar?.setData(0, 0)
binding?.inputBar?.visibility = View.GONE
binding?.inputBar?.visibility = View.INVISIBLE
}
fun onSearchClosed() {
@ -1873,6 +1900,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems)
ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(selectedItems)
ConversationReactionOverlay.Action.BAN_USER -> banUser(selectedItems)
ConversationReactionOverlay.Action.COPY_SESSION_ID -> TODO()
}
}
}

View File

@ -7,9 +7,12 @@ import android.net.Uri
import android.text.InputType
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.isVisible
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewInputBarBinding
@ -27,7 +30,8 @@ import org.thoughtcrime.securesms.util.contains
import org.thoughtcrime.securesms.util.toDp
import org.thoughtcrime.securesms.util.toPx
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate {
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate,
TextView.OnEditorActionListener {
private lateinit var binding: ViewInputBarBinding
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val vMargin by lazy { toDp(4, resources) }
@ -85,11 +89,31 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
}
}
// Edit text
binding.inputBarEditText.setOnEditorActionListener(this)
if (TextSecurePreferences.isEnterSendsEnabled(context)) {
binding.inputBarEditText.imeOptions = EditorInfo.IME_ACTION_SEND
binding.inputBarEditText.inputType =
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
} else {
binding.inputBarEditText.imeOptions = EditorInfo.IME_ACTION_NONE
binding.inputBarEditText.inputType =
binding.inputBarEditText.inputType or
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
}
val incognitoFlag = if (TextSecurePreferences.isIncognitoKeyboardEnabled(context)) 16777216 else 0
binding.inputBarEditText.imeOptions = binding.inputBarEditText.imeOptions or incognitoFlag // Always use incognito keyboard if setting enabled
binding.inputBarEditText.inputType = binding.inputBarEditText.inputType or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
binding.inputBarEditText.delegate = this
}
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (v === binding.inputBarEditText && actionId == EditorInfo.IME_ACTION_SEND) {
// same as pressing send button
delegate?.sendMessage()
return true
}
return false
}
// endregion
// region Updating

View File

@ -16,8 +16,13 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.*
import java.util.*
import org.session.libsession.utilities.getColorFromAttr
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.InputBarButtonImageViewContainer
import org.thoughtcrime.securesms.util.animateSizeChange
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.toPx
import java.util.Date
class InputBarButton : RelativeLayout {
private val gestureHandler = Handler(Looper.getMainLooper())
@ -43,11 +48,11 @@ class InputBarButton : RelativeLayout {
private val collapsedImageViewPosition by lazy { PointF((expandedSize - collapsedSize) / 2, (expandedSize - collapsedSize) / 2) }
private val colorID by lazy {
if (hasOpaqueBackground) {
R.color.input_bar_button_background_opaque
R.attr.input_bar_button_background_opaque
} else if (isSendButton) {
R.color.accent
R.attr.colorAccent
} else {
R.color.input_bar_button_background
R.attr.input_bar_button_background
}
}
@ -59,9 +64,9 @@ class InputBarButton : RelativeLayout {
val size = collapsedSize.toInt()
result.layoutParams = LayoutParams(size, size)
result.setBackgroundResource(R.drawable.input_bar_button_background)
result.mainColor = resources.getColorWithID(colorID, context.theme)
result.mainColor = context.getColorFromAttr(colorID)
if (hasOpaqueBackground) {
result.strokeColor = resources.getColorWithID(R.color.input_bar_button_background_opaque_border, context.theme)
result.strokeColor = context.getColorFromAttr(R.attr.input_bar_button_background_opaque_border)
}
result
}
@ -72,8 +77,7 @@ class InputBarButton : RelativeLayout {
result.layoutParams = LayoutParams(size, size)
result.scaleType = ImageView.ScaleType.CENTER_INSIDE
result.setImageResource(iconID)
val colorID = if (isSendButton) R.color.black else R.color.text
result.imageTintList = ColorStateList.valueOf(resources.getColorWithID(colorID, context.theme))
result.imageTintList = ColorStateList.valueOf(context.getColorFromAttr(R.attr.input_bar_button_text_color))
result
}
@ -104,13 +108,18 @@ class InputBarButton : RelativeLayout {
fun getIconID() = iconID
fun expand() {
GlowViewUtilities.animateColorChange(context, imageViewContainer, colorID, R.color.accent)
val fromColor = context.getColorFromAttr(colorID)
val toColor = context.getAccentColor()
GlowViewUtilities.animateColorChange(imageViewContainer, fromColor, toColor)
imageViewContainer.animateSizeChange(R.dimen.input_bar_button_collapsed_size, R.dimen.input_bar_button_expanded_size, animationDuration)
animateImageViewContainerPositionChange(collapsedImageViewPosition, expandedImageViewPosition)
}
fun collapse() {
GlowViewUtilities.animateColorChange(context, imageViewContainer, R.color.accent, colorID)
val fromColor = context.getAccentColor()
val toColor = context.getColorFromAttr(colorID)
GlowViewUtilities.animateColorChange(imageViewContainer, fromColor, toColor)
imageViewContainer.animateSizeChange(R.dimen.input_bar_button_expanded_size, R.dimen.input_bar_button_collapsed_size, animationDuration)
animateImageViewContainerPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
}

View File

@ -16,6 +16,7 @@ import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.content.pm.ShortcutInfoCompat
@ -27,6 +28,7 @@ import org.session.libsession.messaging.sending_receiving.leave
import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.GroupUtil.doubleDecodeGroupID
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.toHexString
@ -43,7 +45,6 @@ import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.group
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.getColorWithID
import java.io.IOException
object ConversationMenuHelper {
@ -69,7 +70,7 @@ object ConversationMenuHelper {
val actionView = item.actionView
val iconView = actionView.findViewById<ImageView>(R.id.menu_badge_icon)
val badgeView = actionView.findViewById<TextView>(R.id.expiration_badge)
@ColorInt val color = context.resources.getColorWithID(R.color.text, context.theme)
@ColorInt val color = context.getColorFromAttr(android.R.attr.textColorPrimary)
iconView.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)
badgeView.text = ExpirationUtil.getExpirationAbbreviatedDisplayValue(context, thread.expireMessages)
actionView.setOnClickListener { onOptionsItemSelected(item) }
@ -328,7 +329,7 @@ object ConversationMenuHelper {
}
private fun mute(context: Context, thread: Recipient) {
MuteDialog.show(context) { until: Long ->
MuteDialog.show(ContextThemeWrapper(context, context.theme)) { until: Long ->
DatabaseComponent.get(context).recipientDatabase().setMuted(thread, until)
}
}

View File

@ -22,6 +22,7 @@ import com.google.android.flexbox.FlexboxLayout;
import com.google.android.flexbox.JustifyContent;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.conversation.v2.ViewUtil;
@ -126,14 +127,17 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
int innerPadding = ViewUtil.dpToPx(4);
overflowContainer.setPaddingRelative(innerPadding,innerPadding,innerPadding,innerPadding);
int pixelSize = ViewUtil.dpToPx(1);
for (Reaction reaction : reactions) {
if (container.getChildCount() + 1 >= DEFAULT_THRESHOLD && threshold != Integer.MAX_VALUE && reactions.size() > threshold) {
if (overflowContainer.getParent() == null) {
container.addView(overflowContainer);
ViewGroup.LayoutParams overflowParams = overflowContainer.getLayoutParams();
MarginLayoutParams overflowParams = (MarginLayoutParams) overflowContainer.getLayoutParams();
overflowParams.height = ViewUtil.dpToPx(26);
overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize);
overflowContainer.setLayoutParams(overflowParams);
overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_dialog_background));
overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_background));
}
View pill = buildPill(getContext(), this, reaction, true);
pill.setOnClickListener(v -> {
@ -147,11 +151,10 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
View pill = buildPill(getContext(), this, reaction, false);
pill.setTag(reaction);
pill.setOnTouchListener(this);
container.addView(pill);
int pixelSize = ViewUtil.dpToPx(1);
MarginLayoutParams params = (MarginLayoutParams) pill.getLayoutParams();
params.setMargins(pixelSize, 0, pixelSize, 0);
params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize);
pill.setLayoutParams(params);
container.addView(pill);
}
}
@ -246,7 +249,7 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
if (reaction.userWasSender && !isCompact) {
root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected));
countView.setTextColor(ContextCompat.getColor(context, R.color.reactions_pill_selected_text_color));
countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor));
} else {
if (!isCompact) {
root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background));

View File

@ -12,6 +12,7 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewLinkPreviewBinding
import org.session.libsession.utilities.getColorFromAttr
import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
@ -52,14 +53,13 @@ class LinkPreviewView : LinearLayout {
}
// Title
binding.titleTextView.text = linkPreview.title
val textColorID = if (message.isOutgoing && UiModeUtilities.isDayUiMode(context)) {
R.color.white
val textColorID = if (message.isOutgoing) {
R.attr.message_sent_text_color
} else {
if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.white
R.attr.message_received_text_color
}
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
binding.titleTextView.setTextColor(context.getColorFromAttr(textColorID))
// Body
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
// Corner radii
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
cornerMask.setTopLeftRadius(cornerRadii[0])

View File

@ -1,16 +1,19 @@
package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.widget.LinearLayout
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewOpenGroupInvitationBinding
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.OpenGroupUrlParser
import org.thoughtcrime.securesms.conversation.v2.dialogs.JoinOpenGroupDialog
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.util.getAccentColor
class OpenGroupInvitationView : LinearLayout {
private val binding: ViewOpenGroupInvitationBinding by lazy { ViewOpenGroupInvitationBinding.bind(this) }
@ -26,8 +29,11 @@ class OpenGroupInvitationView : LinearLayout {
val data = umd.kind as UpdateMessageData.Kind.OpenGroupInvitation
this.data = data
val iconID = if (message.isOutgoing) R.drawable.ic_globe else R.drawable.ic_plus
val backgroundColor = if (!message.isOutgoing) context.getAccentColor()
else ContextCompat.getColor(context, R.color.transparent_black_6)
with(binding){
openGroupInvitationIconImageView.setImageResource(iconID)
openGroupInvitationIconBackground.backgroundTintList = ColorStateList.valueOf(backgroundColor)
openGroupTitleTextView.text = data.groupName
openGroupURLTextView.text = OpenGroupUrlParser.trimQueryParameter(data.groupUrl)
openGroupTitleTextView.setTextColor(textColor)

View File

@ -14,13 +14,14 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.ViewQuoteBinding
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.toPx
import javax.inject.Inject
@ -89,8 +90,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
binding.quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
} else if (attachments != null) {
binding.quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
val backgroundColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.accent
val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme)
val backgroundColor = context.getAccentColor()
binding.quoteViewAttachmentPreviewContainer.backgroundTintList = ColorStateList.valueOf(backgroundColor)
binding.quoteViewAttachmentPreviewImageView.isVisible = false
binding.quoteViewAttachmentThumbnailImageView.isVisible = false
@ -120,31 +120,19 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
// region Convenience
@ColorInt private fun getLineColor(isOutgoingMessage: Boolean): Int {
val isLightMode = UiModeUtilities.isDayUiMode(context)
return when {
mode == Mode.Regular && isLightMode || mode == Mode.Draft && isLightMode -> {
ResourcesCompat.getColor(resources, R.color.black, context.theme)
}
mode == Mode.Regular && !isLightMode -> {
if (isOutgoingMessage) {
ResourcesCompat.getColor(resources, R.color.black, context.theme)
} else {
ResourcesCompat.getColor(resources, R.color.accent, context.theme)
}
}
else -> { // Draft & dark mode
ResourcesCompat.getColor(resources, R.color.accent, context.theme)
}
mode == Mode.Regular && !isOutgoingMessage -> context.getColorFromAttr(R.attr.colorAccent)
mode == Mode.Regular -> context.getColorFromAttr(R.attr.message_sent_text_color)
else -> context.getColorFromAttr(R.attr.colorAccent)
}
}
@ColorInt private fun getTextColor(isOutgoingMessage: Boolean): Int {
if (mode == Mode.Draft) { return ResourcesCompat.getColor(resources, R.color.text, context.theme) }
val isLightMode = UiModeUtilities.isDayUiMode(context)
return if (!isOutgoingMessage && !isLightMode) {
ResourcesCompat.getColor(resources, R.color.white, context.theme)
if (mode == Mode.Draft) { return context.getColorFromAttr(android.R.attr.textColorPrimary) }
return if (!isOutgoingMessage) {
context.getColorFromAttr(R.attr.message_received_text_color)
} else {
ResourcesCompat.getColor(resources, R.color.black, context.theme)
context.getColorFromAttr(R.attr.message_sent_text_color)
}
}

View File

@ -15,7 +15,6 @@ import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
@ -32,7 +31,7 @@ import org.session.libsession.messaging.jobs.AttachmentDownloadJob
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
@ -44,8 +43,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.SmsMessageRecord
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.SearchUtil
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale
import kotlin.math.roundToInt
@ -70,9 +68,9 @@ class VisibleMessageContentView : LinearLayout {
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean,
glide: GlideRequests, thread: Recipient, searchQuery: String?, contactIsTrusted: Boolean) {
// Background
val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster)
val colorID = if (message.isOutgoing) R.attr.message_sent_background_color else R.attr.message_received_background_color
val color = ThemeUtil.getThemedColor(context, colorID)
val background = getBackground(message.isOutgoing)
val color = if (message.isOutgoing) context.getAccentColor()
else context.getColorFromAttr(R.attr.message_received_background_color)
val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
background.colorFilter = filter
binding.contentParent.background = background
@ -237,22 +235,8 @@ class VisibleMessageContentView : LinearLayout {
private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean =
listOf<View>(albumThumbnailView, linkPreviewView, voiceMessageView.root, quoteView.root).none { it.isVisible }
private fun getBackground(isOutgoing: Boolean, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean): Drawable {
val isSingleMessage = (isStartOfMessageCluster && isEndOfMessageCluster)
@DrawableRes val backgroundID = when {
isSingleMessage -> {
if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone
}
isStartOfMessageCluster -> {
if (isOutgoing) R.drawable.message_bubble_background_sent_start else R.drawable.message_bubble_background_received_start
}
isEndOfMessageCluster -> {
if (isOutgoing) R.drawable.message_bubble_background_sent_end else R.drawable.message_bubble_background_received_end
}
else -> {
if (isOutgoing) R.drawable.message_bubble_background_sent_middle else R.drawable.message_bubble_background_received_middle
}
}
private fun getBackground(isOutgoing: Boolean): Drawable {
val backgroundID = if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone
return ResourcesCompat.getDrawable(resources, backgroundID, context.theme)!!
}
@ -307,13 +291,14 @@ class VisibleMessageContentView : LinearLayout {
@ColorInt
fun getTextColor(context: Context, message: MessageRecord): Int {
val isDayUiMode = UiModeUtilities.isDayUiMode(context)
val colorID = if (message.isOutgoing) {
R.color.black
val colorAttribute = if (message.isOutgoing) {
// sent
R.attr.message_sent_text_color
} else {
if (isDayUiMode) R.color.black else R.color.white
// received
R.attr.message_received_text_color
}
return context.resources.getColorWithID(colorID, context.theme)
return context.getColorFromAttr(colorAttribute)
}
}
// endregion

View File

@ -16,7 +16,6 @@ import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
@ -29,6 +28,7 @@ import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext
@ -45,7 +45,6 @@ import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toDp
import org.thoughtcrime.securesms.util.toPx
import java.util.Date
@ -280,7 +279,7 @@ class VisibleMessageView : LinearLayout {
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
container.layoutParams = containerParams
if (message.expiresIn > 0 && !message.isPending) {
binding.expirationTimerView.setColorFilter(ResourcesCompat.getColor(resources, R.color.text, context.theme))
binding.expirationTimerView.setColorFilter(context.getColorFromAttr(android.R.attr.textColorPrimary))
binding.expirationTimerView.isInvisible = false
binding.expirationTimerView.setPercentComplete(0.0f)
if (message.expireStarted > 0) {
@ -311,7 +310,7 @@ class VisibleMessageView : LinearLayout {
private fun handleIsSelectedChanged() {
background = if (snIsSelected) {
ColorDrawable(context.resources.getColorWithID(R.color.message_selected, context.theme))
ColorDrawable(context.getColorFromAttr(R.attr.message_selected))
} else {
null
}

View File

@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
@ -64,6 +66,8 @@ open class KThumbnailView: FrameLayout {
typedArray.recycle()
}
val background = ContextCompat.getColor(context, R.color.transparent_black_6)
binding.root.background = ColorDrawable(background)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

View File

@ -15,6 +15,7 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.regex.Pattern
object MentionUtilities {
@ -58,13 +59,12 @@ object MentionUtilities {
}
val result = SpannableString(text)
val isLightMode = UiModeUtilities.isDayUiMode(context)
val color = if (isOutgoingMessage) {
ResourcesCompat.getColor(context.resources, if (isLightMode) R.color.white else R.color.black, context.theme)
} else {
context.getAccentColor()
}
for (mention in mentions) {
val colorID = if (isOutgoingMessage) {
if (isLightMode) R.color.white else R.color.black
} else {
R.color.accent
}
val color = ResourcesCompat.getColor(context.resources, colorID, context.theme)
result.setSpan(ForegroundColorSpan(color), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

View File

@ -7,8 +7,7 @@ import android.graphics.Rect
import android.os.SystemClock
import android.util.AttributeSet
import android.view.View
import androidx.core.content.res.ResourcesCompat
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.getAccentColor
import kotlin.math.sin
class ThumbnailProgressBar: View {
@ -25,7 +24,7 @@ class ThumbnailProgressBar: View {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = ResourcesCompat.getColor(resources, R.color.accent, null)
color = context.getAccentColor()
}
private val objectRect = Rect()

View File

@ -72,6 +72,11 @@ public abstract class Database {
context.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null);
}
protected void notifyRecipientListeners() {
context.getContentResolver().notifyChange(DatabaseContentProviders.Recipient.CONTENT_URI, null);
notifyConversationListListeners();
}
protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
}

View File

@ -38,6 +38,10 @@ public class DatabaseContentProviders {
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.stickerpack");
}
public static class Recipient extends NoopContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.recipient");
}
private static abstract class NoopContentProvider extends ContentProvider {
@Override

View File

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class RecipientDatabase extends Database {
@ -232,6 +233,7 @@ public class RecipientDatabase extends Database {
values.put(COLOR, color.serialize());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setColor(color);
notifyRecipientListeners();
}
public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) {
@ -239,6 +241,7 @@ public class RecipientDatabase extends Database {
values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId));
notifyRecipientListeners();
}
public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) {
@ -246,6 +249,7 @@ public class RecipientDatabase extends Database {
contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setForceSmsSelection(forceSmsSelection);
notifyRecipientListeners();
}
public void setApproved(@NonNull Recipient recipient, boolean approved) {
@ -253,6 +257,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED, approved ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setApproved(approved);
notifyRecipientListeners();
}
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
@ -260,6 +265,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED_ME, approvedMe ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setHasApprovedMe(approvedMe);
notifyRecipientListeners();
}
public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
@ -267,6 +273,24 @@ public class RecipientDatabase extends Database {
values.put(BLOCK, blocked ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setBlocked(blocked);
notifyRecipientListeners();
}
public void setBlocked(@NonNull List<Recipient> recipients, boolean blocked) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(BLOCK, blocked ? 1 : 0);
for (Recipient recipient : recipients) {
db.update(TABLE_NAME, values, ADDRESS + " = ?", new String[]{recipient.getAddress().serialize()});
recipient.resolve().setBlocked(blocked);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyRecipientListeners();
}
public void setMuted(@NonNull Recipient recipient, long until) {
@ -274,6 +298,7 @@ public class RecipientDatabase extends Database {
values.put(MUTE_UNTIL, until);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setMuted(until);
notifyRecipientListeners();
}
/**
@ -287,6 +312,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setNotifyType(notifyType);
notifyConversationListListeners();
notifyRecipientListeners();
}
public void setExpireMessages(@NonNull Recipient recipient, int expiration) {
@ -296,6 +322,7 @@ public class RecipientDatabase extends Database {
values.put(EXPIRE_MESSAGES, expiration);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setExpireMessages(expiration);
notifyRecipientListeners();
}
public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
@ -303,6 +330,7 @@ public class RecipientDatabase extends Database {
values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode);
notifyRecipientListeners();
}
public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) {
@ -310,6 +338,7 @@ public class RecipientDatabase extends Database {
values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setProfileKey(profileKey);
notifyRecipientListeners();
}
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
@ -317,6 +346,7 @@ public class RecipientDatabase extends Database {
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileAvatar(profileAvatar);
notifyRecipientListeners();
}
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
@ -325,6 +355,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setName(profileName);
recipient.resolve().setProfileName(profileName);
notifyRecipientListeners();
}
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
@ -332,6 +363,7 @@ public class RecipientDatabase extends Database {
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setProfileSharing(enabled);
notifyRecipientListeners();
}
public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) {
@ -339,6 +371,7 @@ public class RecipientDatabase extends Database {
contentValues.put(NOTIFICATION_CHANNEL, notificationChannel);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setNotificationChannel(notificationChannel);
notifyRecipientListeners();
}
public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) {
@ -346,6 +379,7 @@ public class RecipientDatabase extends Database {
contentValues.put(REGISTERED, registeredState.getId());
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setRegistered(registeredState);
notifyRecipientListeners();
}
private void updateOrInsert(Address address, ContentValues contentValues) {
@ -365,6 +399,22 @@ public class RecipientDatabase extends Database {
database.endTransaction();
}
public List<Recipient> getBlockedContacts() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1",
null, null, null, null, null);
RecipientReader reader = new RecipientReader(context, cursor);
List<Recipient> returnList = new ArrayList<>();
Recipient current;
while ((current = reader.getNext()) != null) {
returnList.add(current);
}
reader.close();
return returnList;
}
public static class RecipientReader implements Closeable {
private final Context context;

View File

@ -952,4 +952,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
}
override fun unblock(toUnblock: List<Recipient>) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(toUnblock, false)
}
override fun blockedContacts(): List<Recipient> {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
return recipientDb.blockedContacts
}
}

View File

@ -50,6 +50,7 @@ public class ThreadRecord extends DisplayRecord {
private final long expiresIn;
private final long lastSeen;
private final boolean pinned;
private final int recipientHash;
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
@NonNull Recipient recipient, long date, long count, int unreadCount,
@ -65,13 +66,18 @@ public class ThreadRecord extends DisplayRecord {
this.archived = archived;
this.expiresIn = expiresIn;
this.lastSeen = lastSeen;
this.pinned = pinned;
this.pinned = pinned;
this.recipientHash = recipient.hashCode();
}
public @Nullable Uri getSnippetUri() {
return snippetUri;
}
public int getRecipientHash() {
return recipientHash;
}
@Override
public SpannableString getDisplayBody(@NonNull Context context) {
if (isGroupUpdateMessage()) {

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.glide
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.ModelLoader
@ -9,7 +8,7 @@ import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import org.session.libsession.avatars.PlaceholderAvatarPhoto
class PlaceholderAvatarLoader(private val context: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
class PlaceholderAvatarLoader(): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
override fun buildLoadData(
model: PlaceholderAvatarPhoto,
@ -17,14 +16,14 @@ class PlaceholderAvatarLoader(private val context: Context): ModelLoader<Placeho
height: Int,
options: Options
): LoadData<BitmapDrawable> {
return LoadData(model, PlaceholderAvatarFetcher(context, model))
return LoadData(model, PlaceholderAvatarFetcher(model.context, model))
}
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
class Factory(private val context: Context) : ModelLoaderFactory<PlaceholderAvatarPhoto, BitmapDrawable> {
class Factory() : ModelLoaderFactory<PlaceholderAvatarPhoto, BitmapDrawable> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
return PlaceholderAvatarLoader(context)
return PlaceholderAvatarLoader()
}
override fun teardown() {}
}

View File

@ -7,9 +7,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.FragmentCreateGroupBinding
@ -57,6 +60,12 @@ class CreateGroupFragment : Fragment() {
}
binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
binding.recyclerView.adapter = adapter
val divider = ContextCompat.getDrawable(requireActivity(), R.drawable.conversation_menu_divider)!!.let {
DividerItemDecoration(requireActivity(), RecyclerView.VERTICAL).apply {
setDrawable(it)
}
}
binding.recyclerView.addItemDecoration(divider)
var isLoading = false
binding.createClosedGroupButton.setOnClickListener {
if (isLoading) return@setOnClickListener

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.home
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -10,7 +11,7 @@ import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.util.UiModeUtilities
class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListener {
class ConversationOptionsBottomSheet(private val parentContext: Context) : BottomSheetDialogFragment(), View.OnClickListener {
private lateinit var binding: FragmentConversationBottomSheetBinding
//FIXME AC: Supplying a threadRecord directly into the field from an activity
// is not the best idea. It doesn't survive configuration change.
@ -28,8 +29,8 @@ class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClick
var onNotificationTapped: (() -> Unit)? = null
var onSetMuteTapped: ((Boolean) -> Unit)? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentConversationBottomSheetBinding.inflate(inflater, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentConversationBottomSheetBinding.inflate(LayoutInflater.from(parentContext), container, false)
return binding.root
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.home
import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
@ -20,6 +21,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale
class ConversationView : LinearLayout {
@ -41,11 +43,19 @@ class ConversationView : LinearLayout {
// region Updating
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
this.thread = thread
background = if (thread.isPinned) {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_pin, 0)
ContextCompat.getDrawable(context, R.drawable.conversation_pinned_background)
if (thread.isPinned) {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
0,
0,
R.drawable.ic_pin,
0
)
} else {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}
background = if (thread.unreadCount > 0) {
ContextCompat.getDrawable(context, R.drawable.conversation_unread_background)
} else {
ContextCompat.getDrawable(context, R.drawable.conversation_view_background)
}
binding.profilePictureView.root.glide = glide
@ -54,7 +64,9 @@ class ConversationView : LinearLayout {
binding.accentView.setBackgroundResource(R.color.destructive)
binding.accentView.visibility = View.VISIBLE
} else {
binding.accentView.setBackgroundResource(R.color.accent)
val accentColor = context.getAccentColor()
val background = ColorDrawable(accentColor)
binding.accentView.background = background
// Using thread.isRead we can determine if the last message was our own, and display it as 'read' even though previous messages may not be
// This would also not trigger the disappearing message timer which may or may not be desirable
binding.accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE
@ -65,9 +77,9 @@ class ConversationView : LinearLayout {
if (unreadCount < 10000) unreadCount.toString() else "9999+"
}
binding.unreadCountTextView.text = formattedUnreadCount
val textSize = if (unreadCount < 10000) 12.0f else 9.0f
val textSize = if (unreadCount < 1000) 12.0f else 10.0f
binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
binding.unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
binding.unreadCountIndicator.background.setTint(context.getAccentColor())
binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
val senderDisplayName = getUserDisplayName(thread.recipient)
?: thread.recipient.address.toString()

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.home
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -9,6 +8,7 @@ import android.os.Bundle
import android.text.SpannableString
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.preferences.SettingsActivity
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.IP2Country
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show
@ -167,7 +166,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
binding.seedReminderView.isVisible = false
}
setupMessageRequestsBanner()
setupHeaderImage()
// Set up recycler view
binding.globalSearchInputLayout.listener = this
homeAdapter.setHasStableIds(true)
@ -276,12 +274,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
EventBus.getDefault().register(this@HomeActivity)
}
private fun setupHeaderImage() {
val isDayUiMode = UiModeUtilities.isDayUiMode(this)
val headerTint = if (isDayUiMode) R.color.black else R.color.white
binding.sessionHeaderImage.setColorFilter(getColor(headerTint))
}
override fun onInputFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
setSearchShown(true)
@ -296,7 +288,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
binding.recyclerView.isVisible = !isShown
binding.emptyStateContainer.isVisible = (binding.recyclerView.adapter as NewHomeAdapter).itemCount == 0 && binding.recyclerView.isVisible
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
binding.gradientView.isVisible = !isShown
binding.globalSearchRecycler.isVisible = isShown
binding.newConversationButton.isVisible = !isShown
}
@ -405,7 +396,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
}
override fun onLongConversationClick(thread: ThreadRecord) {
val bottomSheet = ConversationOptionsBottomSheet()
val bottomSheet = ConversationOptionsBottomSheet(this)
bottomSheet.thread = thread
bottomSheet.onViewDetailsTapped = {
bottomSheet.dismiss()

View File

@ -28,10 +28,8 @@ class HomeDiffUtil(
if (!sameUnreads) return false
val samePinned = oldItem.isPinned == newItem.isPinned
if (!samePinned) return false
val sameAvatar = oldItem.recipient.profileAvatar == newItem.recipient.profileAvatar
if (!sameAvatar) return false
val sameUsername = oldItem.recipient.name == newItem.recipient.name
if (!sameUsername) return false
val sameRecipientHash = oldItem.recipientHash == newItem.recipientHash
if (!sameRecipientHash) return false
val sameSnippet = oldItem.getDisplayBody(context) == newItem.getDisplayBody(context)
if (!sameSnippet) return false
val sameSendStatus = oldItem.isFailed == newItem.isFailed && oldItem.isDelivered == newItem.isDelivered

View File

@ -20,6 +20,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityPathBinding
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.Snode
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.util.GlowViewUtilities
@ -30,6 +31,7 @@ import org.thoughtcrime.securesms.util.animateSizeChange
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.getColorWithID
class PathActivity : PassphraseRequiredActionBarActivity() {
@ -131,7 +133,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
lineView.layoutParams = lineViewLayoutParams
mainContainer.addView(lineView)
val titleTextView = TextView(this)
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
titleTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
titleTextView.text = title
titleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
@ -144,7 +146,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
mainContainer.addView(titleContainer)
if (subtitle != null) {
val subtitleTextView = TextView(this)
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
subtitleTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
subtitleTextView.text = subtitle
subtitleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
@ -185,7 +187,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private val dotView by lazy {
val result = PathDotView(context)
result.setBackgroundResource(R.drawable.accent_dot)
result.mainColor = resources.getColorWithID(R.color.accent, context.theme)
result.mainColor = context.getAccentColor()
result
}
@ -219,7 +221,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private fun setUpViewHierarchy() {
disableClipping()
val lineView = View(context)
lineView.setBackgroundColor(resources.getColorWithID(R.color.text, context.theme))
lineView.setBackgroundColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
val lineViewHeight = when (location) {
Location.Top, Location.Bottom -> resources.getDimensionPixelSize(R.dimen.path_row_height) / 2
Location.Middle -> resources.getDimensionPixelSize(R.dimen.path_row_height)
@ -255,13 +257,17 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private fun expand() {
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
@ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
GlowViewUtilities.animateShadowColorChange(context, dotView, startColorID, R.color.accent)
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.getAccentColor()
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
}
private fun collapse() {
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
@ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
GlowViewUtilities.animateShadowColorChange(context, dotView, R.color.accent, endColorID)
val startColor = context.getAccentColor()
val endColor = context.resources.getColorWithID(endColorID, context.theme)
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
}
}
// endregion

View File

@ -6,10 +6,10 @@ import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.graphics.Paint
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import network.loki.messenger.R
import org.session.libsession.snode.OnionRequestAPI
import org.thoughtcrime.securesms.util.getColorWithID
@ -46,7 +46,9 @@ class PathStatusView : View {
}
private fun initialize() {
update()
if (!isInEditMode) {
update()
}
setWillNotDraw(false)
}
@ -87,12 +89,14 @@ class PathStatusView : View {
private fun update() {
if (OnionRequestAPI.paths.isNotEmpty()) {
setBackgroundResource(R.drawable.accent_dot)
mainColor = resources.getColorWithID(R.color.accent, context.theme)
sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
val hasPathsColor = context.getColor(R.color.accent_green)
mainColor = hasPathsColor
sessionShadowColor = hasPathsColor
} else {
setBackgroundResource(R.drawable.paths_building_dot)
mainColor = resources.getColorWithID(R.color.paths_building, context.theme)
sessionShadowColor = resources.getColorWithID(R.color.paths_building, context.theme)
val pathsBuildingColor = resources.getColorWithID(R.color.paths_building, context.theme)
mainColor = pathsBuildingColor
sessionShadowColor = pathsBuildingColor
}
}

View File

@ -6,6 +6,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -30,7 +31,7 @@ import org.thoughtcrime.securesms.util.UiModeUtilities
import javax.inject.Inject
@AndroidEntryPoint
class UserDetailsBottomSheet : BottomSheetDialogFragment() {
class UserDetailsBottomSheet: BottomSheetDialogFragment() {
@Inject lateinit var threadDb: ThreadDatabase
@ -41,7 +42,9 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentUserDetailsBottomSheetBinding.inflate(inflater, container, false)
val wrappedContext = ContextThemeWrapper(requireActivity(), requireActivity().theme)
val themedInflater = inflater.cloneInContext(wrappedContext)
binding = FragmentUserDetailsBottomSheetBinding.inflate(themedInflater, container, false)
return binding.root
}

View File

@ -95,7 +95,6 @@ class KeyboardPageSearchView @JvmOverloads constructor(
val iconTint = typedArray.getColorStateList(R.styleable.KeyboardPageSearchView_search_icon_tint) ?: ContextCompat.getColorStateList(context, R.color.signal_icon_tint_tab_selected)
ImageViewCompat.setImageTintList(navButton, iconTint)
ImageViewCompat.setImageTintList(clearButton, iconTint)
input.setHintTextColor(iconTint)
val clickOnly: Boolean = typedArray.getBoolean(R.styleable.KeyboardPageSearchView_click_only, false)
if (clickOnly) {

View File

@ -5,11 +5,11 @@ import android.content.res.ColorStateList
import android.database.Cursor
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -47,7 +47,7 @@ class MessageRequestsAdapter(
}
private fun showPopupMenu(view: MessageRequestView) {
val popupMenu = PopupMenu(context, view)
val popupMenu = PopupMenu(ContextThemeWrapper(context, R.style.PopupMenu_MessageRequests), view)
popupMenu.menuInflater.inflate(R.menu.menu_message_request, popupMenu.menu)
popupMenu.setOnMenuItemClickListener { menuItem ->
if (menuItem.itemId == R.id.menu_delete_message_request) {

View File

@ -73,7 +73,7 @@ public class SignalGlideModule extends AppGlideModule {
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory());
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.notifications;
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
@ -65,17 +64,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
}
private void setLed() {
String ledColor = TextSecurePreferences.getNotificationLedColor(context);
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
if (!ledColor.equals("none")) {
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
setLights(Color.parseColor(ledColor),
Integer.parseInt(blinkPatternArray[0]),
Integer.parseInt(blinkPatternArray[1]));
}
int ledColor = TextSecurePreferences.getNotificationLedColor(context);
setLights(ledColor, 500,2000);
}
public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) {
@ -88,13 +78,6 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
}
}
private String[] parseBlinkPattern(String blinkPattern, String blinkPatternCustom) {
if (blinkPattern.equals("custom"))
blinkPattern = blinkPatternCustom;
return blinkPattern.split(",");
}
protected @NonNull CharSequence trimToDisplayLength(@Nullable CharSequence text) {
text = text == null ? "" : text;

View File

@ -1,13 +1,11 @@
package org.thoughtcrime.securesms.notifications;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.AsyncTask;
@ -221,7 +219,7 @@ public class NotificationChannels {
* channels. Performs database operations and should therefore be invoked on a background thread.
*/
@WorkerThread
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull String color) {
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull Integer color) {
if (!supported()) {
return;
}
@ -472,12 +470,12 @@ public class NotificationChannels {
}
@TargetApi(26)
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull String ledColor) {
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull Integer ledColor) {
if ("none".equals(ledColor)) {
channel.enableLights(false);
} else {
channel.enableLights(true);
channel.setLightColor(Color.parseColor(ledColor));
channel.setLightColor(ledColor);
}
}
@ -509,7 +507,7 @@ public class NotificationChannels {
@WorkerThread
@TargetApi(26)
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull String color) {
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull Integer color) {
RecipientDatabase database = DatabaseComponent.get(context).recipientDatabase();
try (RecipientDatabase.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) {

View File

@ -22,6 +22,7 @@ import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.RemoteInput;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@ -63,9 +64,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
{
super(context, privacy);
setSmallIcon(R.drawable.ic_notification);
setColor(context.getResources().getColor(R.color.textsecure_primary));
setColor(ContextCompat.getColor(context, R.color.accent_green));
setCategory(NotificationCompat.CATEGORY_MESSAGE);
if (!NotificationChannels.supported()) {

View File

@ -11,20 +11,22 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.annotation.ColorRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityPnModeBinding
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ThemeUtil
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.PNModeView
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.show
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.PNModeView
class PNModeActivity : BaseActionBarActivity() {
private lateinit var binding: ActivityPnModeBinding
@ -40,10 +42,10 @@ class PNModeActivity : BaseActionBarActivity() {
with(binding) {
contentView.disableClipping()
fcmOptionView.setOnClickListener { toggleFCM() }
fcmOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
fcmOptionView.mainColor = ThemeUtil.getThemedColor(root.context, R.attr.colorPrimary)
fcmOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
backgroundPollingOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
backgroundPollingOptionView.mainColor = ThemeUtil.getThemedColor(root.context, R.attr.colorPrimary)
backgroundPollingOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
registerButton.setOnClickListener { register() }
}
@ -84,60 +86,60 @@ class PNModeActivity : BaseActionBarActivity() {
}
private fun toggleFCM() = with(binding) {
val accentColor = getAccentColor()
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.transparent, R.color.accent)
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
GlowViewUtilities.animateShadowColorChange(fcmOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
animateStrokeColorChange(fcmOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
selectedOptionView = fcmOptionView
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.accent, R.color.transparent)
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
GlowViewUtilities.animateShadowColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
animateStrokeColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
selectedOptionView = null
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.transparent, R.color.accent)
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
GlowViewUtilities.animateShadowColorChange(fcmOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
animateStrokeColorChange(fcmOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.accent, R.color.transparent)
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
animateStrokeColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
selectedOptionView = fcmOptionView
}
}
}
private fun toggleBackgroundPolling() = with(binding) {
val accentColor = getAccentColor()
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.transparent, R.color.accent)
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
animateStrokeColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
selectedOptionView = backgroundPollingOptionView
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.accent, R.color.transparent)
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
animateStrokeColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
selectedOptionView = null
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.transparent, R.color.accent)
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
animateStrokeColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.accent, R.color.transparent)
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
GlowViewUtilities.animateShadowColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
animateStrokeColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
selectedOptionView = backgroundPollingOptionView
}
}
}
private fun animateStrokeColorChange(bubble: PNModeView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val startColor = resources.getColorWithID(startColorID, theme)
val endColor = resources.getColorWithID(endColorID, theme)
private fun animateStrokeColorChange(bubble: PNModeView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
animation.addUpdateListener { animator ->

View File

@ -12,12 +12,13 @@ import android.widget.Toast
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivitySeedBinding
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.utilities.hexEncodedPrivateKey
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.getAccentColor
class SeedActivity : BaseActionBarActivity() {
@ -41,7 +42,7 @@ class SeedActivity : BaseActionBarActivity() {
setContentView(binding.root)
supportActionBar!!.title = resources.getString(R.string.activity_seed_title)
val seedReminderViewTitle = SpannableString("You're almost finished! 90%") // Intentionally not yet translated
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
seedReminderViewTitle.setSpan(ForegroundColorSpan(getAccentColor()), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
with(binding) {
seedReminderView.title = seedReminderViewTitle
seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_2)
@ -55,7 +56,7 @@ class SeedActivity : BaseActionBarActivity() {
}
index += 1
}
seedTextView.setTextColor(resources.getColorWithID(R.color.accent, theme))
seedTextView.setTextColor(getAccentColor())
seedTextView.text = redactedSeed
seedTextView.setOnLongClickListener { revealSeed(); true }
revealButton.setOnLongClickListener { revealSeed(); true }
@ -67,7 +68,7 @@ class SeedActivity : BaseActionBarActivity() {
// region Updating
private fun revealSeed() {
val seedReminderViewTitle = SpannableString("Account secured! 100%") // Intentionally not yet translated
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 17, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
seedReminderViewTitle.setSpan(ForegroundColorSpan(getAccentColor()), 17, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
with(binding) {
seedReminderView.title = seedReminderViewTitle
seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_3)
@ -75,7 +76,7 @@ class SeedActivity : BaseActionBarActivity() {
val seedTextViewLayoutParams = seedTextView.layoutParams as LinearLayout.LayoutParams
seedTextViewLayoutParams.height = seedTextView.height
seedTextView.layoutParams = seedTextViewLayoutParams
seedTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
seedTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
seedTextView.text = seed
}
TextSecurePreferences.setHasViewedSeed(this, true)

View File

@ -22,8 +22,8 @@ import androidx.fragment.app.Fragment;
import com.annimon.stream.Stream;
import com.annimon.stream.function.Consumer;
import org.thoughtcrime.securesms.util.LRUCache;
import org.session.libsession.utilities.ServiceUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import java.lang.ref.WeakReference;
import java.security.SecureRandom;
@ -353,7 +353,7 @@ public class Permissions {
Context context = this.context.get();
if (context != null) {
new AlertDialog.Builder(context)
new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog)
.setTitle(R.string.Permissions_permission_required)
.setMessage(message)
.setPositiveButton(R.string.Permissions_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context)))

View File

@ -4,8 +4,6 @@ package org.thoughtcrime.securesms.permissions;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Color;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@ -14,13 +12,18 @@ import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import network.loki.messenger.R;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.session.libsession.utilities.ViewUtil;
import network.loki.messenger.R;
public class RationaleDialog {
public static AlertDialog.Builder createFor(@NonNull Context context, @NonNull String message, @DrawableRes int... drawables) {
View view = LayoutInflater.from(context).inflate(R.layout.permissions_rationale_dialog, null);
view.setClipToOutline(true);
ViewGroup header = view.findViewById(R.id.header_container);
TextView text = view.findViewById(R.id.message);
@ -47,7 +50,7 @@ public class RationaleDialog {
text.setText(message);
return new AlertDialog.Builder(context, R.style.Theme_TextSecure_Dialog_Rationale).setView(view);
return new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog).setView(view);
}
}

View File

@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.preferences
import android.app.AlertDialog
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityBlockedContactsBinding
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@AndroidEntryPoint
class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener {
lateinit var binding: ActivityBlockedContactsBinding
val viewModel: BlockedContactsViewModel by viewModels()
val adapter = BlockedContactsAdapter()
override fun onClick(v: View?) {
if (v === binding.unblockButton && adapter.getSelectedItems().isNotEmpty()) {
val contactsToUnblock = adapter.getSelectedItems()
// show dialog
val title = if (contactsToUnblock.size == 1) {
getString(R.string.Unblock_dialog__title_single, contactsToUnblock.first().name)
} else {
getString(R.string.Unblock_dialog__title_multiple)
}
val message = if (contactsToUnblock.size == 1) {
getString(R.string.Unblock_dialog__message, contactsToUnblock.first().name)
} else {
val stringBuilder = StringBuilder()
val iterator = contactsToUnblock.iterator()
var numberAdded = 0
while (iterator.hasNext() && numberAdded < 3) {
val nextRecipient = iterator.next()
if (numberAdded > 0) stringBuilder.append(", ")
stringBuilder.append(nextRecipient.name)
numberAdded++
}
val overflow = contactsToUnblock.size - numberAdded
if (overflow > 0) {
stringBuilder.append(" ")
val string = resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow)
stringBuilder.append(string.format(overflow))
}
getString(R.string.Unblock_dialog__message, stringBuilder.toString())
}
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.continue_2) { d, _ ->
viewModel.unblock(contactsToUnblock)
d.dismiss()
}
.setNegativeButton(R.string.cancel) { d, _ ->
d.dismiss()
}
.show()
}
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
binding = ActivityBlockedContactsBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerView.adapter = adapter
viewModel.subscribe(this)
.observe(this) { newState ->
adapter.submitList(newState.blockedContacts)
val isEmpty = newState.blockedContacts.isEmpty()
binding.emptyStateMessageTextView.isVisible = isEmpty
binding.nonEmptyStateGroup.isVisible = !isEmpty
}
binding.unblockButton.setOnClickListener(this)
}
}

View File

@ -0,0 +1,68 @@
package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R
import network.loki.messenger.databinding.BlockedContactLayoutBinding
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.mms.GlideApp
class BlockedContactsAdapter: ListAdapter<Recipient,BlockedContactsAdapter.ViewHolder>(RecipientDiffer()) {
class RecipientDiffer: DiffUtil.ItemCallback<Recipient>() {
override fun areItemsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem === newItem
override fun areContentsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem == newItem
}
private val selectedItems = mutableListOf<Recipient>()
fun getSelectedItems() = selectedItems
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.blocked_contact_layout, parent, false)
return ViewHolder(itemView)
}
private fun toggleSelection(recipient: Recipient, isSelected: Boolean, position: Int) {
if (isSelected) {
selectedItems -= recipient
} else {
selectedItems += recipient
}
notifyItemChanged(position)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val recipient = getItem(position)
val isSelected = recipient in selectedItems
holder.bind(recipient, isSelected) {
toggleSelection(recipient, isSelected, position)
}
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.binding.profilePictureView.root.recycle()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val glide = GlideApp.with(itemView)
val binding = BlockedContactLayoutBinding.bind(itemView)
fun bind(recipient: Recipient, isSelected: Boolean, toggleSelection: () -> Unit) {
binding.recipientName.text = recipient.name
with (binding.profilePictureView.root) {
glide = this@ViewHolder.glide
update(recipient)
}
binding.root.setOnClickListener { toggleSelection() }
binding.selectButton.isSelected = isSelected
}
}
}

View File

@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.preferences
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class BlockedContactsLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs)

View File

@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.preferences
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import android.view.View
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder
class BlockedContactsPreference @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null) : PreferenceCategory(context, attributeSet), View.OnClickListener {
override fun onClick(v: View?) {
if (v is BlockedContactsLayout) {
val intent = Intent(context, BlockedContactsActivity::class.java)
context.startActivity(intent)
}
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val itemView = holder.itemView
itemView.setOnClickListener(this)
}
}

View File

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.preferences
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.cash.copper.flow.observeQuery
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.Storage
import javax.inject.Inject
@HiltViewModel
class BlockedContactsViewModel @Inject constructor(private val storage: Storage): ViewModel() {
private val executor = viewModelScope + SupervisorJob()
private val listUpdateChannel = Channel<Unit>(capacity = Channel.CONFLATED)
private val _contacts = MutableLiveData(BlockedContactsViewState(emptyList()))
fun subscribe(context: Context): LiveData<BlockedContactsViewState> {
executor.launch(IO) {
context.contentResolver
.observeQuery(DatabaseContentProviders.Recipient.CONTENT_URI)
.onStart {
listUpdateChannel.trySend(Unit)
}
.onEach {
listUpdateChannel.trySend(Unit)
}
.collect()
}
executor.launch(IO) {
for (update in listUpdateChannel) {
val blockedContactState = BlockedContactsViewState(storage.blockedContacts().sortedBy { it.name })
withContext(Main) {
_contacts.value = blockedContactState
}
}
}
return _contacts
}
fun unblock(toUnblock: List<Recipient>) {
storage.unblock(toUnblock)
}
data class BlockedContactsViewState(
val blockedContacts: List<Recipient>
)
}

View File

@ -2,11 +2,7 @@ package org.thoughtcrime.securesms.preferences
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.UiMode
import org.thoughtcrime.securesms.util.UiModeUtilities
class ChangeUiModeDialog : DialogFragment() {
@ -16,19 +12,8 @@ class ChangeUiModeDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
val displayNameList = UiMode.values().map { getString(it.displayNameRes) }.toTypedArray()
val activeUiMode = UiModeUtilities.getUserSelectedUiMode(context)
return AlertDialog.Builder(context)
.setSingleChoiceItems(displayNameList, activeUiMode.ordinal) { _, selectedItemIdx: Int ->
val uiMode = UiMode.values()[selectedItemIdx]
UiModeUtilities.setUserSelectedUiMode(context, uiMode)
dismiss()
requireActivity().recreate()
}
.setTitle(R.string.dialog_ui_mode_title)
.setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
.create()
return android.app.AlertDialog.Builder(context)
.setTitle("TODO: remove this")
.show()
}
}

View File

@ -3,14 +3,13 @@ package org.thoughtcrime.securesms.preferences
import android.os.Bundle
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment
class ChatSettingsActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_fragment_wrapper)
supportActionBar!!.title = resources.getString(R.string.activity_chat_settings_title)
supportActionBar!!.title = resources.getString(R.string.activity_conversations_settings_title)
val fragment = ChatsPreferenceFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)

View File

@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@ -13,8 +15,8 @@ import network.loki.messenger.databinding.DialogClearAllDataBinding
import org.session.libsession.snode.SnodeAPI
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
class ClearAllDataDialog : BaseDialog() {
private lateinit var binding: DialogClearAllDataBinding
@ -26,9 +28,6 @@ class ClearAllDataDialog : BaseDialog() {
}
var clearJob: Job? = null
set(value) {
field = value
}
var step = Steps.INFO_PROMPT
set(value) {
@ -38,19 +37,27 @@ class ClearAllDataDialog : BaseDialog() {
override fun setContentView(builder: AlertDialog.Builder) {
binding = DialogClearAllDataBinding.inflate(LayoutInflater.from(requireContext()))
val device = RadioOption("deviceOnly", requireContext().getString(R.string.dialog_clear_all_data_clear_device_only))
val network = RadioOption("deviceAndNetwork", requireContext().getString(R.string.dialog_clear_all_data_clear_device_and_network))
var selectedOption = device
val optionAdapter = RadioOptionAdapter { selectedOption = it }
binding.recyclerView.apply {
adapter = optionAdapter
addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
setHasFixedSize(true)
}
optionAdapter.submitList(listOf(device, network))
binding.cancelButton.setOnClickListener {
if (step == Steps.NETWORK_PROMPT) {
clearAllData(false)
} else if (step != Steps.DELETING) {
dismiss()
}
dismiss()
}
binding.clearAllDataButton.setOnClickListener {
when(step) {
Steps.INFO_PROMPT -> step = Steps.NETWORK_PROMPT
Steps.NETWORK_PROMPT -> {
clearAllData(true)
Steps.INFO_PROMPT -> if (selectedOption == network) {
step = Steps.NETWORK_PROMPT
} else {
clearAllData(false)
}
Steps.NETWORK_PROMPT -> clearAllData(true)
Steps.DELETING -> { /* do nothing intentionally */ }
}
}
@ -64,17 +71,14 @@ class ClearAllDataDialog : BaseDialog() {
when (step) {
Steps.INFO_PROMPT -> {
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_explanation)
binding.cancelButton.setText(R.string.cancel)
binding.clearAllDataButton.setText(R.string.delete)
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_message)
}
else -> {
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_network_explanation)
binding.cancelButton.setText(R.string.dialog_clear_all_data_local_only)
binding.clearAllDataButton.setText(R.string.dialog_clear_all_data_clear_network)
Steps.NETWORK_PROMPT -> {
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_clear_device_and_network_confirmation)
}
Steps.DELETING -> { /* do nothing intentionally */ }
}
binding.recyclerView.isGone = step == Steps.NETWORK_PROMPT
binding.cancelButton.isVisible = !isLoading
binding.clearAllDataButton.isVisible = !isLoading
binding.progressBar.isVisible = isLoading

View File

@ -2,9 +2,18 @@ package org.thoughtcrime.securesms.preferences;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
@ -12,19 +21,30 @@ import androidx.preference.PreferenceGroupAdapter;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
import org.thoughtcrime.securesms.conversation.v2.ViewUtil;
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat;
import network.loki.messenger.R;
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
public static final int SINGLE_TYPE = 21;
public static final int TOP_TYPE = 22;
public static final int MIDDLE_TYPE = 23;
public static final int BOTTOM_TYPE = 24;
public static final int CATEGORY_TYPE = 25;
public int horizontalPadding;
public int verticalPadding;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
horizontalPadding = ViewUtil.dpToPx(requireContext(), 36);
verticalPadding = ViewUtil.dpToPx(requireContext(), 8);
}
@Override
@ -33,6 +53,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
View lv = getView().findViewById(android.R.id.list);
if (lv != null) lv.setPadding(0, 0, 0, 0);
setDivider(null);
}
@Override
@ -57,17 +78,100 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
@SuppressLint("RestrictedApi")
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
return new PreferenceGroupAdapter(preferenceScreen) {
@NonNull
@Override
public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
PreferenceViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
return viewHolder;
}
private int getPreferenceType(int position) {
Preference preference = getItem(position);
if (preference instanceof PreferenceCategory) {
return CATEGORY_TYPE;
}
boolean isStart = isTop(position);
boolean isEnd = isBottom(position);
if (isStart && isEnd) {
// always show full
return SINGLE_TYPE;
} else {
if (isStart) {
return TOP_TYPE;
} else if (isEnd) {
return BOTTOM_TYPE;
} else {
return MIDDLE_TYPE;
}
}
}
private boolean isTop(int position) {
if (position == 0) {
return true;
}
Preference previous = getItem(position - 1);
return previous instanceof PreferenceCategory;
}
private boolean isBottom(int position) {
int size = getItemCount();
if (position == size - 1) {
// last one
return true;
}
Preference next = getItem(position + 1);
return next instanceof PreferenceCategory;
}
public Drawable getBackground(Context context, int position) {
int viewType = getPreferenceType(position);
Drawable background;
switch (viewType) {
case SINGLE_TYPE:
background = ContextCompat.getDrawable(context, R.drawable.preference_single);
break;
case TOP_TYPE:
background = ContextCompat.getDrawable(context, R.drawable.preference_top);
break;
case MIDDLE_TYPE:
background = ContextCompat.getDrawable(context, R.drawable.preference_middle);
break;
case BOTTOM_TYPE:
background = ContextCompat.getDrawable(context, R.drawable.preference_bottom);
break;
default:
background = null;
break;
}
return background;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
Preference preference = getItem(position);
if (preference instanceof PreferenceCategory) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
layoutParams.topMargin = 0;
layoutParams.bottomMargin = 0;
holder.itemView.setLayoutParams(layoutParams);
setZeroPaddingToLayoutChildren(holder.itemView);
} else {
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
if (iconFrame != null) {
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
}
Drawable background = getBackground(holder.itemView.getContext(), position);
holder.itemView.setBackground(background);
TextView titleView = holder.itemView.findViewById(android.R.id.title);
if (titleView != null) {
((TextView) titleView).setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
}
boolean isTop = isTop(position);
boolean isBottom = isBottom(position);
holder.itemView.setPadding(horizontalPadding, isTop ? verticalPadding : 0, horizontalPadding, isBottom ? verticalPadding : 0);
}
}
};

View File

@ -0,0 +1,94 @@
package org.thoughtcrime.securesms.preferences
import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.preference.Preference
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.permissions.Permissions
class HelpSettingsActivity: PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
setContentView(R.layout.activity_fragment_wrapper)
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, HelpSettingsFragment())
.commit()
}
}
class HelpSettingsFragment: CorrectedPreferenceFragment() {
companion object {
private const val EXPORT_LOGS = "export_logs"
private const val TRANSLATE = "translate_session"
private const val FEEDBACK = "feedback"
private const val FAQ = "faq"
private const val SUPPORT = "support"
private const val CROWDIN_URL = "https://crowdin.com/project/session-android"
private const val FEEDBACK_URL = "https://getsession.org/survey"
private const val FAQ_URL = "https://getsession.org/faq"
private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us"
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_help)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
preference ?: return false
return when (preference.key) {
EXPORT_LOGS -> {
shareLogs()
true
}
TRANSLATE -> {
openLink(CROWDIN_URL)
true
}
FEEDBACK -> {
openLink(FEEDBACK_URL)
true
}
FAQ -> {
openLink(FAQ_URL)
true
}
SUPPORT -> {
openLink(SUPPORT_URL)
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
private fun shareLogs() {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
.onAnyDenied {
Toast.makeText(requireActivity(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
}
.onAllGranted {
ShareLogsDialog().show(parentFragmentManager,"Share Logs Dialog")
}
.execute()
}
private fun openLink(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(requireActivity(), "Can't open URL", Toast.LENGTH_LONG).show()
}
}
}

View File

@ -0,0 +1,42 @@
package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.preference.ListPreference
import androidx.recyclerview.widget.DividerItemDecoration
import network.loki.messenger.databinding.DialogListPreferenceBinding
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
class ListPreferenceDialog(
private val listPreference: ListPreference,
private val dialogListener: () -> Unit
) : BaseDialog() {
private lateinit var binding: DialogListPreferenceBinding
override fun setContentView(builder: AlertDialog.Builder) {
binding = DialogListPreferenceBinding.inflate(LayoutInflater.from(requireContext()))
binding.titleTextView.text = listPreference.dialogTitle
binding.messageTextView.text = listPreference.dialogMessage
binding.closeButton.setOnClickListener {
dismiss()
}
val options = listPreference.entryValues.zip(listPreference.entries) { value, title ->
RadioOption(value.toString(), title.toString())
}
val valueIndex = listPreference.findIndexOfValue(listPreference.value)
val optionAdapter = RadioOptionAdapter(valueIndex) {
listPreference.value = it.value
dismiss()
dialogListener.invoke()
}
binding.recyclerView.apply {
adapter = optionAdapter
addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
setHasFixedSize(true)
}
optionAdapter.submitList(options)
builder.setView(binding.root)
builder.setCancelable(false)
}
}

View File

@ -4,10 +4,10 @@ package org.thoughtcrime.securesms.preferences;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import network.loki.messenger.R;
import java.util.Arrays;
import network.loki.messenger.R;
public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceFragment {
protected class ListSummaryListener implements Preference.OnPreferenceChangeListener {

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.preferences;
import static android.app.Activity.RESULT_OK;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
@ -9,20 +11,19 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import android.text.TextUtils;
import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.session.libsession.utilities.TextSecurePreferences;
import network.loki.messenger.R;
import static android.app.Activity.RESULT_OK;
public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment {
@SuppressWarnings("unused")
@ -42,28 +43,14 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
return true;
});
Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF);
if (NotificationChannels.supported()) {
ledBlinkPref.setVisible(false);
TextSecurePreferences.setNotificationRingtone(getContext(), NotificationChannels.getMessageRingtone(getContext()).toString());
TextSecurePreferences.setNotificationVibrateEnabled(getContext(), NotificationChannels.getMessageVibrate(getContext()));
} else {
ledBlinkPref.setOnPreferenceChangeListener(new ListSummaryListener());
initializeListSummary((ListPreference) ledBlinkPref);
}
this.findPreference(TextSecurePreferences.LED_COLOR_PREF)
.setOnPreferenceChangeListener(new LedColorChangeListener());
this.findPreference(TextSecurePreferences.RINGTONE_PREF)
.setOnPreferenceChangeListener(new RingtoneSummaryListener());
this.findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF)
.setOnPreferenceChangeListener(new ListSummaryListener());
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)
.setOnPreferenceChangeListener(new NotificationPrivacyListener());
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)
.setOnPreferenceChangeListener(new ListSummaryListener());
this.findPreference(TextSecurePreferences.VIBRATE_PREF)
.setOnPreferenceChangeListener((preference, newValue) -> {
NotificationChannels.updateMessageVibrate(getContext(), (boolean) newValue);
@ -86,8 +73,17 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
return true;
});
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_COLOR_PREF));
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF));
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)
.setOnPreferenceClickListener(preference -> {
ListPreference listPreference = (ListPreference) preference;
listPreference.setDialogMessage(R.string.preferences_notifications__content_message);
new ListPreferenceDialog(listPreference, () -> {
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
return null;
}).show(getChildFragmentManager(), "ListPreferenceDialog");
return true;
});
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
if (NotificationChannels.supported()) {
@ -99,8 +95,6 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
startActivity(intent);
return true;
});
} else {
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF));
}
initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF));
@ -183,20 +177,4 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
}
}
@SuppressLint("StaticFieldLeak")
private class LedColorChangeListener extends ListSummaryListener {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
if (NotificationChannels.supported()) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
NotificationChannels.updateMessagesLedColor(getActivity(), (String) value);
return null;
}
}.execute();
}
return super.onPreferenceChange(preference, value);
}
}
}

View File

@ -3,14 +3,14 @@ package org.thoughtcrime.securesms.preferences
import android.os.Bundle
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment
class PrivacySettingsActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_fragment_wrapper)
val fragment = AppProtectionPreferenceFragment()
val fragment =
PrivacySettingsPreferenceFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()

View File

@ -12,6 +12,7 @@ import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
@ -23,14 +24,11 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.CallNotificationBuilder;
import org.thoughtcrime.securesms.util.IntentUtils;
import java.util.concurrent.TimeUnit;
import kotlin.jvm.functions.Function1;
import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
import network.loki.messenger.BuildConfig;
import network.loki.messenger.R;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment {
public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFragment {
@Override
public void onAttach(Activity activity) {
@ -42,7 +40,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
super.onCreate(paramBundle);
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
@ -56,7 +53,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)).setChecked(isEnabled);
if (isEnabled && !CallNotificationBuilder.areNotificationsEnabled(requireActivity())) {
// show a dialog saying that calls won't work properly if you don't have notifications on at a system level
new AlertDialog.Builder(requireActivity())
new AlertDialog.Builder(new ContextThemeWrapper(requireActivity(), R.style.ThemeOverlay_Session_AlertDialog))
.setTitle(R.string.CallNotificationBuilder_system_notification_title)
.setMessage(R.string.CallNotificationBuilder_system_notification_message)
.setPositiveButton(R.string.activity_notification_settings_title, (d, w) -> {
@ -100,20 +97,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
@Override
public void onResume() {
super.onResume();
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
initializeScreenLockTimeoutSummary();
}
}
private void initializeScreenLockTimeoutSummary() {
long timeoutSeconds = TextSecurePreferences.getScreenLockTimeout(getContext());
long hours = TimeUnit.SECONDS.toHours(timeoutSeconds);
long minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - (TimeUnit.SECONDS.toHours(timeoutSeconds) * 60 );
long seconds = TimeUnit.SECONDS.toSeconds(timeoutSeconds) - (TimeUnit.SECONDS.toMinutes(timeoutSeconds) * 60);
findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT)
.setSummary(timeoutSeconds <= 0 ? getString(R.string.AppProtectionPreferenceFragment_none) :
String.format("%02d:%02d:%02d", hours, minutes, seconds));
}
private void initializeVisibility() {
@ -143,25 +126,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
}
}
private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
if (duration == 0) {
TextSecurePreferences.setScreenLockTimeout(getContext(), 0);
} else {
long timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration);
TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
}
initializeScreenLockTimeoutSummary();
}, 0).show();
return true;
}
}
private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -215,20 +179,16 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
boolean val = (boolean) newValue;
if (val) {
// check if we've shown the info dialog and check for microphone permissions
if (TextSecurePreferences.setShownCallWarning(context.requireContext())) {
new AlertDialog.Builder(context.requireContext())
.setTitle(R.string.dialog_voice_video_title)
.setMessage(R.string.dialog_voice_video_message)
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
requestMicrophonePermission();
})
.setNegativeButton(R.string.cancel, (d, w) -> {
new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog))
.setTitle(R.string.dialog_voice_video_title)
.setMessage(R.string.dialog_voice_video_message)
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
requestMicrophonePermission();
})
.setNegativeButton(R.string.cancel, (d, w) -> {
})
.show();
} else {
requestMicrophonePermission();
}
})
.show();
return false;
} else {
return true;

View File

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.preferences
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R
import network.loki.messenger.databinding.ItemSelectableBinding
import org.thoughtcrime.securesms.mms.GlideApp
class RadioOptionAdapter(
var selectedOptionPosition: Int = 0,
private val onClickListener: (RadioOption) -> Unit
) : ListAdapter<RadioOption, RadioOptionAdapter.ViewHolder>(RadioOptionDiffer()) {
class RadioOptionDiffer: DiffUtil.ItemCallback<RadioOption>() {
override fun areItemsTheSame(oldItem: RadioOption, newItem: RadioOption) = oldItem === newItem
override fun areContentsTheSame(oldItem: RadioOption, newItem: RadioOption) = oldItem == newItem
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_selectable, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val option = getItem(position)
val isSelected = position == selectedOptionPosition
holder.bind(option, isSelected) {
onClickListener(it)
selectedOptionPosition = position
notifyDataSetChanged()
}
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val glide = GlideApp.with(itemView)
val binding = ItemSelectableBinding.bind(itemView)
fun bind(option: RadioOption, isSelected: Boolean, toggleSelection: (RadioOption) -> Unit) {
binding.titleTextView.text = option.title
binding.root.setOnClickListener { toggleSelection(option) }
binding.selectButton.isSelected = isSelected
}
}
}
data class RadioOption(
val value: String,
val title: String
)

View File

@ -30,7 +30,7 @@ class SeedDialog : BaseDialog() {
override fun setContentView(builder: AlertDialog.Builder) {
val binding = DialogSeedBinding.inflate(LayoutInflater.from(requireContext()))
binding.seedTextView.text = seed
binding.cancelButton.setOnClickListener { dismiss() }
binding.closeButton.setOnClickListener { dismiss() }
binding.copyButton.setOnClickListener { copySeed() }
builder.setView(binding.root)
}

View File

@ -8,10 +8,11 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Parcelable
import android.util.SparseArray
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
@ -39,11 +40,11 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show
@ -67,6 +68,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
companion object {
const val updatedProfileResultCode = 1234
private const val SCROLL_STATE = "SCROLL_STATE"
}
// region Lifecycle
@ -94,24 +96,31 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
notificationsButton.setOnClickListener { showNotificationSettings() }
messageRequestsButton.setOnClickListener { showMessageRequests() }
chatsButton.setOnClickListener { showChatSettings() }
sendInvitationButton.setOnClickListener { sendInvitation() }
faqButton.setOnClickListener { showFAQ() }
surveyButton.setOnClickListener { showSurvey() }
helpTranslateButton.setOnClickListener { helpTranslate() }
appearanceButton.setOnClickListener { showAppearanceSettings() }
inviteFriendButton.setOnClickListener { sendInvitation() }
helpButton.setOnClickListener { showHelp() }
seedButton.setOnClickListener { showSeed() }
clearAllDataButton.setOnClickListener { clearAllData() }
debugLogButton.setOnClickListener { shareLogs() }
val isLightMode = UiModeUtilities.isDayUiMode(this@SettingsActivity)
oxenLogoImageView.setImageResource(if (isLightMode) R.drawable.oxen_light_mode else R.drawable.oxen_dark_mode)
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val scrollBundle = SparseArray<Parcelable>()
binding.scrollView.saveHierarchyState(scrollBundle)
outState.putSparseParcelableArray(SCROLL_STATE, scrollBundle)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
savedInstanceState.getSparseParcelableArray<Parcelable>(SCROLL_STATE)?.let { scrollBundle ->
binding.scrollView.restoreHierarchyState(scrollBundle)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.settings_general, menu)
// Update UI mode menu icon
val uiMode = UiModeUtilities.getUserSelectedUiMode(this)
menu.findItem(R.id.action_change_theme).icon!!.level = uiMode.ordinal
return true
}
@ -121,10 +130,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
showQRCode()
true
}
R.id.action_change_theme -> {
ChangeUiModeDialog().show(supportFragmentManager, ChangeUiModeDialog.TAG)
true
}
else -> super.onOptionsItemSelected(item)
}
}
@ -295,6 +300,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
push(intent)
}
private fun showAppearanceSettings() {
val intent = Intent(this, AppearanceSettingsActivity::class.java)
push(intent)
}
private fun sendInvitation() {
val intent = Intent()
intent.action = Intent.ACTION_SEND
@ -305,14 +315,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
startActivity(chooser)
}
private fun showFAQ() {
try {
val url = "https://getsession.org/faq"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
}
private fun showHelp() {
val intent = Intent(this, HelpSettingsActivity::class.java)
push(intent)
}
private fun showPath() {
@ -320,26 +325,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
show(intent)
}
private fun showSurvey() {
try {
val url = "https://getsession.org/survey"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
}
}
private fun helpTranslate() {
try {
val url = "https://crowdin.com/project/session-android"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
}
}
private fun showSeed() {
SeedDialog().show(supportFragmentManager, "Recovery Phrase Dialog")
}
@ -348,20 +333,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
}
private fun shareLogs() {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
.onAnyDenied {
Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
}
.onAllGranted {
ShareLogsDialog().show(supportFragmentManager,"Share Logs Dialog")
}
.execute()
}
// endregion
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {

View File

@ -0,0 +1,153 @@
package org.thoughtcrime.securesms.preferences.appearance
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.widget.SwitchCompat
import androidx.core.view.children
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityAppearanceSettingsBinding
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_LIGHT
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.util.ThemeState
@AndroidEntryPoint
class AppearanceSettingsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener {
companion object {
private const val SCROLL_PARCEL = "scroll_parcel"
}
val viewModel: AppearanceSettingsViewModel by viewModels()
lateinit var binding : ActivityAppearanceSettingsBinding
var currentTheme: ThemeState? = null
private val accentColors
get() = mapOf(
binding.accentGreen to R.style.PrimaryGreen,
binding.accentBlue to R.style.PrimaryBlue,
binding.accentYellow to R.style.PrimaryYellow,
binding.accentPink to R.style.PrimaryPink,
binding.accentPurple to R.style.PrimaryPurple,
binding.accentOrange to R.style.PrimaryOrange,
binding.accentRed to R.style.PrimaryRed
)
private val themeViews
get() = listOf(
binding.themeOptionClassicDark,
binding.themeRadioClassicDark,
binding.themeOptionClassicLight,
binding.themeRadioClassicLight,
binding.themeOptionOceanDark,
binding.themeRadioOceanDark,
binding.themeOptionOceanLight,
binding.themeRadioOceanLight
)
override fun onClick(v: View?) {
v ?: return
val accents = accentColors
val themes = themeViews
if (v in accents) {
val entry = accents[v]
entry?.let { viewModel.setNewAccent(it) }
} else if (v in themes) {
val currentBase = if (currentTheme?.theme == R.style.Classic_Dark || currentTheme?.theme == R.style.Classic_Light) R.style.Classic else R.style.Ocean
val (mappedStyle, newBase) = when (v) {
binding.themeOptionClassicDark, binding.themeRadioClassicDark -> CLASSIC_DARK to R.style.Classic
binding.themeOptionClassicLight, binding.themeRadioClassicLight -> CLASSIC_LIGHT to R.style.Classic
binding.themeOptionOceanDark, binding.themeRadioOceanDark -> OCEAN_DARK to R.style.Ocean
binding.themeOptionOceanLight, binding.themeRadioOceanLight -> OCEAN_LIGHT to R.style.Ocean
else -> throw NullPointerException("Invalid style for view [$v]")
}
viewModel.setNewStyle(mappedStyle)
if (currentBase != newBase) {
if (newBase == R.style.Ocean) {
viewModel.setNewAccent(R.style.PrimaryBlue)
} else if (newBase == R.style.Classic) {
viewModel.setNewAccent(R.style.PrimaryGreen)
}
}
} else if (v == binding.systemSettingsSwitch) {
viewModel.setNewFollowSystemSettings((v as SwitchCompat).isChecked)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val scrollParcelArray = SparseArray<Parcelable>()
binding.scrollView.saveHierarchyState(scrollParcelArray)
outState.putSparseParcelableArray(SCROLL_PARCEL, scrollParcelArray)
}
private fun updateSelectedTheme(themeStyle: Int) {
mapOf(
R.style.Classic_Dark to binding.themeRadioClassicDark,
R.style.Classic_Light to binding.themeRadioClassicLight,
R.style.Ocean_Dark to binding.themeRadioOceanDark,
R.style.Ocean_Light to binding.themeRadioOceanLight
).forEach { (style, view) ->
view.isChecked = themeStyle == style
}
}
private fun updateSelectedAccent(accentStyle: Int) {
accentColors.forEach { (view, style) ->
view.isSelected = style == accentStyle
}
}
private fun updateFollowSystemToggle(followSystemSettings: Boolean) {
binding.systemSettingsSwitch.isChecked = followSystemSettings
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
binding = ActivityAppearanceSettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
savedInstanceState?.let { bundle ->
val scrollStateParcel = bundle.getSparseParcelableArray<Parcelable>(SCROLL_PARCEL)
if (scrollStateParcel != null) {
binding.scrollView.restoreHierarchyState(scrollStateParcel)
}
}
supportActionBar!!.title = getString(R.string.activity_settings_message_appearance_button_title)
with (binding) {
// accent toggles
accentContainer.children.forEach { view ->
view.setOnClickListener(this@AppearanceSettingsActivity)
}
// theme toggles
themeViews.forEach {
it.setOnClickListener(this@AppearanceSettingsActivity)
}
// system settings toggle
systemSettingsSwitch.setOnClickListener(this@AppearanceSettingsActivity)
}
lifecycleScope.launchWhenResumed {
viewModel.uiState.collectLatest { themeState ->
val (theme, accent, followSystem) = themeState
updateSelectedTheme(theme)
updateSelectedAccent(accent)
updateFollowSystemToggle(followSystem)
if (currentTheme != null && currentTheme != themeState) {
recreate()
} else {
currentTheme = themeState
}
}
}
}
}

View File

@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.preferences.appearance
import androidx.annotation.StyleRes
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.util.ThemeState
import org.thoughtcrime.securesms.util.themeState
import javax.inject.Inject
@HiltViewModel
class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSecurePreferences) : ViewModel() {
private val _uiState = MutableStateFlow(prefs.themeState())
val uiState: StateFlow<ThemeState> = _uiState
fun setNewAccent(@StyleRes newAccentColorStyle: Int) {
prefs.setAccentColorStyle(newAccentColorStyle)
// update UI state
_uiState.value = prefs.themeState()
}
fun setNewStyle(newThemeStyle: String) {
prefs.setThemeStyle(newThemeStyle)
// update UI state
_uiState.value = prefs.themeState()
}
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
prefs.setFollowSystemSettings(followSystemSettings)
_uiState.value = prefs.themeState()
}
}

View File

@ -1,107 +0,0 @@
/**
* Copyright (C) 2017 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.preferences.widgets;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.widget.ImageView;
import network.loki.messenger.R;
/**
* List preference that disables dependents when set to "none", similar to a CheckBoxPreference.
*
* @author Taylor Kline
*/
public class LEDColorListPreference extends ListPreference {
private static final String TAG = LEDColorListPreference.class.getSimpleName();
private ImageView colorImageView;
public LEDColorListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setWidgetLayoutResource(R.layout.led_color_preference_widget);
}
public LEDColorListPreference(Context context) {
super(context);
setWidgetLayoutResource(R.layout.led_color_preference_widget);
}
@Override
public void setValue(String value) {
CharSequence oldEntry = getEntry();
super.setValue(value);
CharSequence newEntry = getEntry();
if (oldEntry != newEntry) {
notifyDependencyChange(shouldDisableDependents());
}
if (value != null) setPreviewColor(value);
}
@Override
public boolean shouldDisableDependents() {
CharSequence newEntry = getValue();
boolean shouldDisable = newEntry.equals("none");
return shouldDisable || super.shouldDisableDependents();
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
this.colorImageView = (ImageView)view.findViewById(R.id.color_view);
setPreviewColor(getValue());
}
@Override
public void setSummary(CharSequence summary) {
super.setSummary(null);
}
private void setPreviewColor(@NonNull String value) {
int color;
switch (value) {
case "green": color = getContext().getResources().getColor(R.color.green_500); break;
case "red": color = getContext().getResources().getColor(R.color.red_500); break;
case "blue": color = getContext().getResources().getColor(R.color.blue_500); break;
case "yellow": color = getContext().getResources().getColor(R.color.yellow_500); break;
case "cyan": color = getContext().getResources().getColor(R.color.cyan_500); break;
case "magenta": color = getContext().getResources().getColor(R.color.pink_500); break;
case "white": color = getContext().getResources().getColor(R.color.white); break;
default: color = getContext().getResources().getColor(R.color.transparent); break;
}
if (colorImageView != null) {
GradientDrawable drawable = new GradientDrawable();
drawable.setShape(GradientDrawable.OVAL);
drawable.setColor(color);
colorImageView.setImageDrawable(drawable);
}
}
}

View File

@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.preferences.widgets
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class NotificationSettingsPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
override fun onFinishInflate() {
super.onFinishInflate()
// TODO: if we want do the spans
}
}

View File

@ -68,20 +68,14 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (ThemeUtil.isDarkTheme(requireContext())) {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_TextSecure_BottomSheetDialog_Fixed_ReactWithAny);
} else {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_TextSecure_Light_BottomSheetDialog_Fixed_ReactWithAny);
}
// setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Session_BottomSheet);
super.onCreate(savedInstanceState);
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState)
{
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.reactions_bottom_sheet_dialog_fragment, container, false);
}
@ -126,7 +120,7 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
View customView = tab.getCustomView();
TextView text = customView.findViewById(R.id.reactions_pill_count);
customView.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.reaction_pill_background_selected));
text.setTextColor(ContextCompat.getColor(requireContext(), R.color.reactions_pill_selected_text_color));
text.setTextColor(ThemeUtil.getThemedColor(requireContext(), R.attr.reactionsPillSelectedTextColor));
}
@Override
@ -134,7 +128,7 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
View customView = tab.getCustomView();
TextView text = customView.findViewById(R.id.reactions_pill_count);
customView.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.reaction_pill_dialog_background));
text.setTextColor(ContextCompat.getColor(requireContext(), R.color.reactions_pill_text_color));
text.setTextColor(ThemeUtil.getThemedColor(requireContext(), R.attr.reactionsPillNormalTextColor));
}
@Override
public void onTabReselected(TabLayout.Tab tab) {}

View File

@ -14,8 +14,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
@ -24,7 +22,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
@ -80,7 +77,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.Widget_TextSecure_ReactWithAny);
}
@Override
@ -93,22 +89,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
.setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18))
.build();
MaterialShapeDrawable dialogBackground = new MaterialShapeDrawable(shapeAppearanceModel);
dialogBackground.setTint(ContextCompat.getColor(requireContext(), R.color.react_with_any_background));
dialog.getBehavior().addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (bottomSheet.getBackground() != dialogBackground) {
ViewCompat.setBackground(bottomSheet, dialogBackground);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
});
boolean shadows = requireArguments().getBoolean(ARG_SHADOWS, true);
if (!shadows) {
Window window = dialog.getWindow();

View File

@ -1,14 +1,14 @@
package org.thoughtcrime.securesms.util
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
@ -67,4 +67,35 @@ interface ActivityDispatcher {
}
fun dispatchIntent(body: (Context)->Intent?)
fun showDialog(baseDialog: BaseDialog, tag: String? = null)
}
}
fun TextSecurePreferences.themeState(): ThemeState {
val themeStyle = getThemeStyle().getThemeStyle()
val accentStyle = getAccentColorStyle() ?: themeStyle.getDefaultAccentColor()
val followSystem = getFollowSystemSettings()
return ThemeState(
themeStyle,
accentStyle,
followSystem
)
}
@StyleRes
fun String.getThemeStyle(): Int = when (this) {
TextSecurePreferences.CLASSIC_DARK -> R.style.Classic_Dark
TextSecurePreferences.CLASSIC_LIGHT -> R.style.Classic_Light
TextSecurePreferences.OCEAN_DARK -> R.style.Ocean_Dark
TextSecurePreferences.OCEAN_LIGHT -> R.style.Ocean_Light
else -> throw NullPointerException("The style [$this] is not supported")
}
@StyleRes
fun Int.getDefaultAccentColor(): Int =
if (this == R.style.Ocean_Dark || this == R.style.Ocean_Light) R.style.PrimaryBlue
else R.style.PrimaryGreen
data class ThemeState (
@StyleRes val theme: Int,
@StyleRes val accentStyle: Int,
val followSystem: Boolean
)

View File

@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.util
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
@ -11,8 +12,6 @@ import android.widget.RelativeLayout
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toPx
import kotlin.math.roundToInt
interface GlowView {
@ -22,7 +21,7 @@ interface GlowView {
object GlowViewUtilities {
fun animateColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
fun animateColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
@ -34,7 +33,17 @@ object GlowViewUtilities {
animation.start()
}
fun animateShadowColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
fun animateColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
animation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
view.mainColor = color
}
animation.start()
}
fun animateShadowColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
@ -45,6 +54,17 @@ object GlowViewUtilities {
}
animation.start()
}
fun animateShadowColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
animation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
view.sessionShadowColor = color
}
animation.start()
}
}
class PNModeView : LinearLayout, GlowView {

View File

@ -2,46 +2,12 @@ package org.thoughtcrime.securesms.util
import android.content.Context
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import network.loki.messenger.R
/**
* Day/night UI mode related utilities.
* @see <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme">Official Documentation</a>
*/
object UiModeUtilities {
private const val PREF_KEY_SELECTED_UI_MODE = "SELECTED_UI_MODE"
@JvmStatic
fun setUserSelectedUiMode(context: Context, uiMode: UiMode) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit()
.putString(PREF_KEY_SELECTED_UI_MODE, uiMode.name)
.apply()
AppCompatDelegate.setDefaultNightMode(uiMode.nightModeValue)
}
@JvmStatic
fun getUserSelectedUiMode(context: Context): UiMode {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val selectedUiModeName = prefs.getString(PREF_KEY_SELECTED_UI_MODE, UiMode.SYSTEM_DEFAULT.name)!!
var selectedUiMode: UiMode
try {
selectedUiMode = UiMode.valueOf(selectedUiModeName)
} catch (e: IllegalArgumentException) {
// Cannot recognize UiMode constant from the given string.
selectedUiMode = UiMode.SYSTEM_DEFAULT
}
return selectedUiMode
}
@JvmStatic
fun setupUiModeToUserSelected(context: Context) {
val selectedUiMode = getUserSelectedUiMode(context)
setUserSelectedUiMode(context, selectedUiMode)
}
/**
* Whether the application UI is in the light mode
@ -52,14 +18,5 @@ object UiModeUtilities {
val uiModeNightBit = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return uiModeNightBit == Configuration.UI_MODE_NIGHT_NO
}
}
enum class UiMode(
@StringRes
val displayNameRes: Int,
val nightModeValue: Int) {
DAY(R.string.dialog_ui_mode_option_day, AppCompatDelegate.MODE_NIGHT_NO),
NIGHT(R.string.dialog_ui_mode_option_night, AppCompatDelegate.MODE_NIGHT_YES),
SYSTEM_DEFAULT(R.string.dialog_ui_mode_option_system_default, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}

View File

@ -7,8 +7,11 @@ import android.animation.ValueAnimator
import android.content.Context
import android.graphics.PointF
import android.graphics.Rect
import androidx.annotation.DimenRes
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.DimenRes
import network.loki.messenger.R
import org.session.libsession.utilities.getColorFromAttr
import android.view.inputmethod.InputMethodManager
fun View.contains(point: PointF): Boolean {
@ -22,6 +25,9 @@ val View.hitRect: Rect
return rect
}
@ColorInt
fun Context.getAccentColor() = getColorFromAttr(R.attr.colorAccent)
fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int, animationDuration: Long = 250) {
val startSize = resources.getDimension(startSizeID)
val endSize = resources.getDimension(endSizeID)

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:textColorPrimary" android:state_selected="true"/>
<item android:color="?android:textColorTertiary"/>
</selector>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="1000dp" />
<solid android:color="@color/core_grey_05" />
<stroke android:color="@color/white" android:width="1dp" />
</shape>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="1000dp" />
<solid android:color="@color/core_grey_05" />
<stroke android:color="?colorAccent" android:width="1dp" />
</shape>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="1000dp" />
<solid android:color="@color/core_grey_05" />
</shape>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/unimportant_button_background" />
<corners android:radius="@dimen/medium_button_corner_radius" />
<stroke android:width="@dimen/border_thickness" android:color="?android:textColorPrimary" />
</shape>

View File

@ -3,6 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/accent" />
<solid android:color="?colorAccent" />
</shape>

View File

@ -4,6 +4,6 @@
android:shape="rectangle">
<corners android:radius="@dimen/context_menu_corner_radius" />
<solid android:color="@color/background_dialog" />
<solid android:color="?colorPrimary" />
</shape>

View File

@ -4,7 +4,7 @@
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
<item>
<selector>
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
<item android:drawable="?colorAccent" android:state_selected="true" />
</selector>
</item>
</ripple>

View File

@ -4,7 +4,7 @@
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
<item>
<selector>
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
<item android:drawable="?colorAccent" android:state_selected="true" />
</selector>
</item>
</ripple>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp"/>
<solid android:color="?conversation_menu_border_color"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:angle="90"
android:endColor="@color/transparent"
android:startColor="?conversation_menu_background_color"/>
</shape>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/cell_selected">
android:color="?colorCellRipple">
<item>
<color android:color="?attr/conversation_pinned_background_color" />
<color android:color="?conversation_pinned_background_color" />
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?colorCellRipple">
<item>
<color android:color="?conversation_unread_background_color" />
</item>
</ripple>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/cell_selected">
android:color="?colorCellRipple">
<item>
<color android:color="@color/cell_background" />
<color android:color="?colorCellBackground" />
</item>
</ripple>

View File

@ -3,11 +3,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/dialog_background_color" />
<solid android:color="?dialog_background_color" />
<corners
android:topLeftRadius="@dimen/dialog_corner_radius"
android:topRightRadius="@dimen/dialog_corner_radius" />
<!-- <stroke android:width="@dimen/border_thickness" android:color="@color/dialog_border" />-->
<stroke android:width="@dimen/border_thickness" android:color="?dialog_border" />
</shape>

View File

@ -5,6 +5,6 @@
<solid android:color="?attr/dialog_background_color" />
<corners android:radius="@dimen/dialog_corner_radius" />
<corners android:radius="?dialogCornerRadius" />
</shape>

View File

@ -5,8 +5,8 @@
<gradient
android:angle="90"
android:startColor="@color/default_background_start"
android:endColor="@color/default_background_end"
android:startColor="?default_background_start"
android:endColor="?default_background_end"
android:type="linear" />
</shape>

View File

@ -5,7 +5,7 @@
<solid android:color="@color/transparent" />
<corners android:radius="@dimen/medium_button_corner_radius" />
<corners android:radius="@dimen/dialog_button_corner_radius" />
<stroke android:width="@dimen/border_thickness" android:color="?android:textColorPrimary" />
<stroke android:width="@dimen/border_thickness" android:color="@color/transparent" />
</shape>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="26dp"
android:viewportWidth="26"
android:viewportHeight="26">
<group>
<clip-path
android:pathData="M0.722,0.471h25v25h-25z"/>
<path
android:pathData="M18.554,17.315C18.379,17.315 18.213,17.245 18.09,17.122L9.084,8.112C8.962,7.989 8.892,7.818 8.892,7.643C8.892,7.468 8.962,7.302 9.089,7.179L9.391,6.881C9.658,6.619 9.938,6.343 10.214,6.067L11.676,4.605C12.876,3.405 14.08,2.201 15.284,1.006C15.428,0.861 15.582,0.765 15.717,0.677C15.761,0.651 15.809,0.62 15.849,0.59C15.958,0.511 16.089,0.471 16.225,0.471H16.597C16.715,0.471 16.829,0.502 16.93,0.563C16.956,0.577 16.983,0.59 17.004,0.603C17.105,0.655 17.245,0.725 17.372,0.848C17.731,1.185 18.016,1.47 18.274,1.741C18.511,1.991 18.642,2.288 18.673,2.604C19.04,2.63 19.369,2.827 19.745,3.203L22.066,5.524C22.596,6.054 22.722,6.658 22.434,7.28C23.046,6.991 23.642,7.109 24.172,7.63L24.408,7.862C24.645,8.094 24.881,8.326 25.104,8.567C25.249,8.72 25.363,8.887 25.463,9.031C25.503,9.088 25.542,9.145 25.586,9.202C25.669,9.316 25.717,9.456 25.717,9.596V9.968C25.717,10.112 25.669,10.248 25.586,10.362C25.546,10.419 25.507,10.476 25.468,10.533C25.363,10.682 25.244,10.857 25.087,11.01C23.655,12.446 22.223,13.882 20.792,15.318L19.128,16.982C19.128,16.982 19.08,17.052 19.071,17.061C18.957,17.21 18.782,17.302 18.594,17.315C18.581,17.315 18.567,17.315 18.554,17.315ZM10.481,7.652L18.541,15.712L19.868,14.386C21.3,12.954 22.731,11.518 24.163,10.082C24.237,10.007 24.312,9.898 24.395,9.78C24.312,9.661 24.229,9.548 24.15,9.46C23.94,9.232 23.716,9.018 23.497,8.799L23.257,8.563C23.143,8.449 23.086,8.436 23.086,8.436C23.086,8.436 23.029,8.436 22.889,8.532L20.77,9.972C20.652,10.051 20.507,10.139 20.297,10.174C20.039,10.222 19.78,10.108 19.636,9.889C19.491,9.67 19.491,9.386 19.636,9.167L19.741,9.005C19.802,8.908 19.868,8.812 19.929,8.716L21.181,6.855C21.326,6.64 21.313,6.614 21.151,6.448L18.83,4.127C18.646,3.943 18.581,3.913 18.567,3.908C18.567,3.908 18.497,3.917 18.261,4.031C18.222,4.048 18.182,4.066 18.13,4.088L18.038,4.127C17.793,4.232 17.508,4.175 17.32,3.987C17.132,3.799 17.075,3.514 17.175,3.269L17.232,3.138C17.272,3.037 17.307,2.954 17.346,2.871C17.412,2.726 17.381,2.691 17.333,2.643C17.096,2.389 16.829,2.126 16.492,1.811C16.483,1.807 16.466,1.798 16.448,1.789H16.44C16.352,1.846 16.269,1.899 16.225,1.938C15.021,3.133 13.821,4.337 12.618,5.537L11.151,6.999C10.932,7.218 10.708,7.442 10.49,7.652H10.481Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M4.618,25.471H3.922C3.852,25.471 3.782,25.458 3.716,25.436C3.708,25.436 3.66,25.419 3.655,25.414C2.425,25.183 1.501,24.451 1.006,23.295C0.914,23.081 0.857,22.866 0.809,22.678C0.792,22.604 0.77,22.529 0.748,22.455C0.73,22.393 0.722,22.332 0.722,22.271V21.579C0.722,21.535 0.722,21.492 0.735,21.448C0.941,20.441 1.374,19.697 2.066,19.162C2.364,18.935 2.718,18.668 3.095,18.418C4.561,17.45 6.028,16.483 7.499,15.524C7.981,15.209 8.033,14.894 8.025,14.626C8.016,14.394 7.911,14.193 7.688,13.97L7.254,13.536C6.86,13.147 6.47,12.757 6.081,12.363C5.437,11.71 5.433,10.866 6.072,10.217C6.475,9.81 6.878,9.403 7.285,8.996L7.758,8.523C7.797,8.484 7.841,8.444 7.88,8.418C8.143,8.208 8.541,8.208 8.778,8.449L17.766,17.437C17.889,17.56 17.959,17.731 17.959,17.906C17.959,18.081 17.885,18.247 17.762,18.37L17.495,18.633C17.272,18.851 17.044,19.07 16.821,19.294L16.488,19.626C16.317,19.797 16.151,19.964 15.98,20.13C15.328,20.76 14.487,20.756 13.839,20.117C13.305,19.591 12.775,19.057 12.25,18.528C12.018,18.295 11.812,18.186 11.58,18.177C11.199,18.16 10.927,18.317 10.678,18.698L10.639,18.76C9.688,20.218 8.703,21.728 7.714,23.199C7.39,23.681 7.022,24.136 6.619,24.561C6.203,24.999 5.638,25.279 4.894,25.419L4.82,25.445C4.758,25.463 4.693,25.476 4.627,25.476L4.618,25.471ZM4.036,24.158H4.513C4.561,24.14 4.605,24.132 4.649,24.123C5.113,24.035 5.446,23.882 5.665,23.65C6.015,23.282 6.339,22.879 6.619,22.464C7.604,20.997 8.585,19.495 9.535,18.037L9.575,17.976C10.205,17.008 11.037,16.833 11.628,16.859C12.193,16.881 12.718,17.131 13.182,17.599C13.708,18.129 14.233,18.659 14.763,19.18C14.899,19.316 14.929,19.316 15.069,19.18C15.236,19.018 15.398,18.856 15.56,18.694L15.901,18.352C16.059,18.199 16.216,18.042 16.374,17.893L8.305,9.832L8.213,9.924C7.81,10.327 7.407,10.725 7.009,11.133C6.886,11.255 6.869,11.286 7.018,11.435C7.403,11.824 7.793,12.214 8.178,12.599L8.611,13.033C9.067,13.488 9.312,14.005 9.334,14.565C9.36,15.152 9.185,15.984 8.213,16.618C6.742,17.577 5.275,18.541 3.813,19.508C3.471,19.736 3.138,19.985 2.858,20.2C2.438,20.524 2.171,20.984 2.026,21.645V22.179C2.044,22.236 2.057,22.293 2.07,22.35C2.11,22.507 2.149,22.656 2.202,22.774C2.526,23.532 3.082,23.974 3.905,24.123C3.948,24.132 3.988,24.14 4.023,24.154L4.036,24.158ZM4.312,23.668H4.303C3.331,23.663 2.534,22.866 2.53,21.89C2.53,21.413 2.714,20.962 3.055,20.62C3.392,20.283 3.839,20.095 4.312,20.095H4.321C5.297,20.099 6.094,20.9 6.098,21.872C6.098,22.345 5.914,22.796 5.573,23.138C5.231,23.475 4.785,23.663 4.312,23.663V23.668ZM4.312,21.413C4.189,21.413 4.075,21.461 3.984,21.553C3.892,21.645 3.843,21.763 3.843,21.886C3.843,22.14 4.058,22.35 4.312,22.354C4.43,22.337 4.557,22.306 4.649,22.214C4.741,22.122 4.789,22.004 4.789,21.881C4.789,21.632 4.57,21.413 4.321,21.413H4.312Z"
android:fillColor="#ffffff"/>
</group>
</vector>

View File

@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:tint="?colorAccent">
<path
android:fillColor="@android:color/white"
android:pathData="m12,2.4355c-5.2796,0 -9.5645,4.2848 -9.5645,9.5645 0,5.2796 4.2848,9.5645 9.5645,9.5645 5.2796,0 9.5645,-4.2848 9.5645,-9.5645 0,-5.2796 -4.2848,-9.5645 -9.5645,-9.5645zM12.123,7.9375 L15.6777,11.4922 14.9707,12.1992 12.623,9.8515v6.1797h-1v-6.1797l-1.9961,1.9941 -0.3535,0.3535 -0.707,-0.707 0.3535,-0.3535 3.2031,-3.2012z"

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="26dp"
android:viewportWidth="25"
android:viewportHeight="26">
<path
android:pathData="M19.907,7.674H19.907H4.54H4.54C4.317,7.674 4.095,7.719 3.888,7.806L3.888,7.806C3.681,7.893 3.491,8.023 3.334,8.189C3.176,8.355 3.054,8.554 2.978,8.775L3.922,9.097L2.978,8.775C2.903,8.996 2.877,9.231 2.904,9.465L2.904,9.465L2.904,9.469L4.555,23.412C4.555,23.413 4.555,23.413 4.555,23.414C4.603,23.823 4.807,24.189 5.111,24.447C5.415,24.705 5.798,24.84 6.187,24.84H6.188H18.26H18.26C18.649,24.84 19.032,24.705 19.336,24.447C19.64,24.189 19.844,23.823 19.892,23.414C19.892,23.413 19.892,23.413 19.892,23.412L21.543,9.469L21.544,9.465C21.57,9.231 21.544,8.996 21.469,8.775L21.469,8.775C21.393,8.554 21.271,8.355 21.113,8.189C20.956,8.023 20.766,7.893 20.559,7.806L20.17,8.728L20.559,7.806C20.352,7.719 20.13,7.674 19.907,7.674ZM21.412,1.84H3.031C2.045,1.84 1.149,2.609 1.149,3.674V5.828C1.149,6.893 2.045,7.662 3.031,7.662H21.412C22.398,7.662 23.294,6.893 23.294,5.828V3.674C23.294,2.609 22.398,1.84 21.412,1.84Z"
android:strokeWidth="2"
android:fillColor="#FF3A3A"
android:strokeColor="?colorPrimaryDark"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="22dp"
android:viewportWidth="25"
android:viewportHeight="22">
<path
android:pathData="M1.137,21.463C1.018,21.463 0.899,21.447 0.789,21.406C0.441,21.284 0.222,20.983 0.222,20.649V3.549C0.222,1.856 1.768,0.471 3.68,0.471H20.763C22.666,0.471 24.222,1.848 24.222,3.549V14.68C24.222,16.374 22.675,17.758 20.763,17.758H5.638L1.795,21.227C1.622,21.382 1.384,21.471 1.146,21.471L1.137,21.463ZM3.68,2.1C2.784,2.1 2.052,2.751 2.052,3.549V18.67L4.595,16.374C4.769,16.219 4.998,16.13 5.245,16.13H20.754C21.651,16.13 22.383,15.478 22.383,14.68V3.549C22.383,2.751 21.651,2.1 20.754,2.1H3.671H3.68Z"
android:fillColor="#ffffff"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More