mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-12 01:41:54 +00:00
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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user