New app theming (#913)

* feat: start new app theming feature

* feat: add some theming colours

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

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

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

* feat: global search colours for light and dark ocean

* feat: more styling

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

* feat: add dynamic accent color

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

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

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

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

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

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

* fix: input bar bug fix and preference activity themes

* refactor: new settings menu options

* fix: crash for PNModeActivity.kt

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

* feat: add new appearance settings activity

* refactor: title and VM changes

* fix: correct override

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

* fix: compile errors and missing themes from emoji features

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

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

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

* refactor: add bottom margin

* feat: explore custom preference category

* feat: add the customized session theme for CorrectedPreferenceFragment

* feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment

* refactor: change drawable style and remove explicit dividers

* refactor: remove divider in CorrectedPreferenceFragment

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

* feat: add preference divider elements for settings menu

* refactor: settings menu redesigns

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

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

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

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

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

* feat: add blocked contacts and notif recipient listeners

* feat: add recipient db reader

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

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

* feat: add settings menu vectors

* fix: preview compile error

* refactor: migrating settings menu to new designs

* feat: help menu

* refactor: simplify link opening

* refactor: remove space

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

* refactor: fixing dark and light modes with dialogs

* refactor: popup dialogs use proper themes now

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

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

* refactor: transparency, dialog fixes, notification fix

* refactor: attrs and styles for buttons

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

* fix: fix the trash

* refactor: remove the appearance

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

* refactor: use colors instead of style references

* refactor: theming changes to match designs and feedback

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

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

* refactor: increase setting button height

* Update clear all data dialog

* Update seed dialog

* refactor: more qa feedback changes

* feat: add new TLs and fa-rIR TLs

* Update notification content dialog

* Fix message requests clear all button text color

* feat: re-add screenshot observer

* refactor: make send tint accent color

* feat: add unread background differences

* fix: change unread count indicator

* build: upgrade build numbers

* Fix message requests popupmenu background color

* fix: crash from attr reference in color attribute

* build: upgrade build number

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

* fix: tertiary text for keyboard page search view

* fix: emoji overflow colour differences

* fix: reaction pill dialog background is now correct colour

* Add style to reactions tab layout

* fix: appearance activity reverting primary color at correct time

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

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

* fix: crash in all media containing documents

* fix: reaction dialog heading fixes

* Add style to reactions tab layout

* fix: remove gradient backgrounds

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

* fix: ocean dark unread/read colours

* build; update build number

* build: update build number

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

View File

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

View File

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

View File

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

View File

@ -1,44 +1,103 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import static org.session.libsession.utilities.TextSecurePreferences.SELECTED_ACCENT_COLOR;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; 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; import network.loki.messenger.R;
public abstract class BaseActionBarActivity extends AppCompatActivity { public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName(); 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true); actionBar.setHomeButtonEnabled(true);
} }
super.onCreate(savedInstanceState);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
initializeScreenshotSecurity(); initializeScreenshotSecurity(true);
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this)); DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
String name = getResources().getString(R.string.app_name); String name = getResources().getString(R.string.app_name);
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground); Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
int color = getResources().getColor(R.color.app_icon_background); int color = getResources().getColor(R.color.app_icon_background);
setTaskDescription(new ActivityManager.TaskDescription(name, icon, color)); setTaskDescription(new ActivityManager.TaskDescription(name, icon, color));
if (!currentThemeState.equals(ActivityUtilitiesKt.themeState(getPreferences()))) {
recreate();
}
}
@Override
protected void onPause() {
super.onPause();
initializeScreenshotSecurity(false);
} }
@Override @Override
@ -49,11 +108,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
return true; return true;
} }
private void initializeScreenshotSecurity() { private void initializeScreenshotSecurity(boolean isResume) {
if (TextSecurePreferences.isScreenSecurityEnabled(this)) { if (!isResume) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else { } else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
} }
} }

View File

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

View File

@ -107,7 +107,7 @@ class ProfilePictureView @JvmOverloads constructor(
if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return
val signalProfilePicture = recipient.contactPhoto val signalProfilePicture = recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject 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 != "") { if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.clear(imageView) glide.clear(imageView)
glide.load(signalProfilePicture) glide.load(signalProfilePicture)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,9 +7,12 @@ import android.net.Uri
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.util.AttributeSet import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewInputBarBinding 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.toDp
import org.thoughtcrime.securesms.util.toPx 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 lateinit var binding: ViewInputBarBinding
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val vMargin by lazy { toDp(4, resources) } private val vMargin by lazy { toDp(4, resources) }
@ -85,11 +89,31 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
} }
} }
// Edit text // 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 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.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 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 // endregion
// region Updating // region Updating

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.regex.Pattern import java.util.regex.Pattern
object MentionUtilities { object MentionUtilities {
@ -58,13 +59,12 @@ object MentionUtilities {
} }
val result = SpannableString(text) val result = SpannableString(text)
val isLightMode = UiModeUtilities.isDayUiMode(context) 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) { 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(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) result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }

View File

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

View File

@ -72,6 +72,11 @@ public abstract class Database {
context.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null); 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) { protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId)); cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
} }

View File

@ -38,6 +38,10 @@ public class DatabaseContentProviders {
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.stickerpack"); public static 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 { private static abstract class NoopContentProvider extends ContentProvider {
@Override @Override

View File

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class RecipientDatabase extends Database { public class RecipientDatabase extends Database {
@ -232,6 +233,7 @@ public class RecipientDatabase extends Database {
values.put(COLOR, color.serialize()); values.put(COLOR, color.serialize());
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setColor(color); recipient.resolve().setColor(color);
notifyRecipientListeners();
} }
public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) { public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) {
@ -239,6 +241,7 @@ public class RecipientDatabase extends Database {
values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId); values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId)); recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId));
notifyRecipientListeners();
} }
public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) { 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); contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setForceSmsSelection(forceSmsSelection); recipient.resolve().setForceSmsSelection(forceSmsSelection);
notifyRecipientListeners();
} }
public void setApproved(@NonNull Recipient recipient, boolean approved) { public void setApproved(@NonNull Recipient recipient, boolean approved) {
@ -253,6 +257,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED, approved ? 1 : 0); values.put(APPROVED, approved ? 1 : 0);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setApproved(approved); recipient.resolve().setApproved(approved);
notifyRecipientListeners();
} }
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) { public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
@ -260,6 +265,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED_ME, approvedMe ? 1 : 0); values.put(APPROVED_ME, approvedMe ? 1 : 0);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setHasApprovedMe(approvedMe); recipient.resolve().setHasApprovedMe(approvedMe);
notifyRecipientListeners();
} }
public void setBlocked(@NonNull Recipient recipient, boolean blocked) { public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
@ -267,6 +273,24 @@ public class RecipientDatabase extends Database {
values.put(BLOCK, blocked ? 1 : 0); values.put(BLOCK, blocked ? 1 : 0);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setBlocked(blocked); 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) { public void setMuted(@NonNull Recipient recipient, long until) {
@ -274,6 +298,7 @@ public class RecipientDatabase extends Database {
values.put(MUTE_UNTIL, until); values.put(MUTE_UNTIL, until);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setMuted(until); recipient.resolve().setMuted(until);
notifyRecipientListeners();
} }
/** /**
@ -287,6 +312,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setNotifyType(notifyType); recipient.resolve().setNotifyType(notifyType);
notifyConversationListListeners(); notifyConversationListListeners();
notifyRecipientListeners();
} }
public void setExpireMessages(@NonNull Recipient recipient, int expiration) { public void setExpireMessages(@NonNull Recipient recipient, int expiration) {
@ -296,6 +322,7 @@ public class RecipientDatabase extends Database {
values.put(EXPIRE_MESSAGES, expiration); values.put(EXPIRE_MESSAGES, expiration);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setExpireMessages(expiration); recipient.resolve().setExpireMessages(expiration);
notifyRecipientListeners();
} }
public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) { 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()); values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode); recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode);
notifyRecipientListeners();
} }
public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { 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)); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setProfileKey(profileKey); recipient.resolve().setProfileKey(profileKey);
notifyRecipientListeners();
} }
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) { public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
@ -317,6 +346,7 @@ public class RecipientDatabase extends Database {
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar); contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileAvatar(profileAvatar); recipient.resolve().setProfileAvatar(profileAvatar);
notifyRecipientListeners();
} }
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) { public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
@ -325,6 +355,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setName(profileName); recipient.resolve().setName(profileName);
recipient.resolve().setProfileName(profileName); recipient.resolve().setProfileName(profileName);
notifyRecipientListeners();
} }
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) { public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
@ -332,6 +363,7 @@ public class RecipientDatabase extends Database {
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0); contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.setProfileSharing(enabled); recipient.setProfileSharing(enabled);
notifyRecipientListeners();
} }
public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) { public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) {
@ -339,6 +371,7 @@ public class RecipientDatabase extends Database {
contentValues.put(NOTIFICATION_CHANNEL, notificationChannel); contentValues.put(NOTIFICATION_CHANNEL, notificationChannel);
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.setNotificationChannel(notificationChannel); recipient.setNotificationChannel(notificationChannel);
notifyRecipientListeners();
} }
public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) { public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) {
@ -346,6 +379,7 @@ public class RecipientDatabase extends Database {
contentValues.put(REGISTERED, registeredState.getId()); contentValues.put(REGISTERED, registeredState.getId());
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);
recipient.setRegistered(registeredState); recipient.setRegistered(registeredState);
notifyRecipientListeners();
} }
private void updateOrInsert(Address address, ContentValues contentValues) { private void updateOrInsert(Address address, ContentValues contentValues) {
@ -365,6 +399,22 @@ public class RecipientDatabase extends Database {
database.endTransaction(); 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 { public static class RecipientReader implements Closeable {
private final Context context; private final Context context;

View File

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

View File

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

View File

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

View File

@ -7,9 +7,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.FragmentCreateGroupBinding import network.loki.messenger.databinding.FragmentCreateGroupBinding
@ -57,6 +60,12 @@ class CreateGroupFragment : Fragment() {
} }
binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() } binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
binding.recyclerView.adapter = adapter 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 var isLoading = false
binding.createClosedGroupButton.setOnClickListener { binding.createClosedGroupButton.setOnClickListener {
if (isLoading) return@setOnClickListener if (isLoading) return@setOnClickListener

View File

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

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.home
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater 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.database.model.ThreadRecord
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale import java.util.Locale
class ConversationView : LinearLayout { class ConversationView : LinearLayout {
@ -41,11 +43,19 @@ class ConversationView : LinearLayout {
// region Updating // region Updating
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) { fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
this.thread = thread this.thread = thread
background = if (thread.isPinned) { if (thread.isPinned) {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_pin, 0) binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
ContextCompat.getDrawable(context, R.drawable.conversation_pinned_background) 0,
0,
R.drawable.ic_pin,
0
)
} else { } else {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) 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) ContextCompat.getDrawable(context, R.drawable.conversation_view_background)
} }
binding.profilePictureView.root.glide = glide binding.profilePictureView.root.glide = glide
@ -54,7 +64,9 @@ class ConversationView : LinearLayout {
binding.accentView.setBackgroundResource(R.color.destructive) binding.accentView.setBackgroundResource(R.color.destructive)
binding.accentView.visibility = View.VISIBLE binding.accentView.visibility = View.VISIBLE
} else { } 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 // 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 // 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 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+" if (unreadCount < 10000) unreadCount.toString() else "9999+"
} }
binding.unreadCountTextView.text = formattedUnreadCount 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.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) binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
val senderDisplayName = getUserDisplayName(thread.recipient) val senderDisplayName = getUserDisplayName(thread.recipient)
?: thread.recipient.address.toString() ?: thread.recipient.address.toString()

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -30,7 +31,7 @@ import org.thoughtcrime.securesms.util.UiModeUtilities
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class UserDetailsBottomSheet : BottomSheetDialogFragment() { class UserDetailsBottomSheet: BottomSheetDialogFragment() {
@Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var threadDb: ThreadDatabase
@ -41,7 +42,9 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 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 return binding.root
} }

View File

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

View File

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

View File

@ -73,7 +73,7 @@ public class SignalGlideModule extends AppGlideModule {
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context)); registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory()); registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.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()); registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
} }

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.notifications;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@ -65,17 +64,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
} }
private void setLed() { private void setLed() {
String ledColor = TextSecurePreferences.getNotificationLedColor(context); int ledColor = TextSecurePreferences.getNotificationLedColor(context);
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context); setLights(ledColor, 500,2000);
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]));
}
} }
public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) { 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) { protected @NonNull CharSequence trimToDisplayLength(@Nullable CharSequence text) {
text = text == null ? "" : text; text = text == null ? "" : text;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,7 @@ package org.thoughtcrime.securesms.preferences
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment 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() { class ChangeUiModeDialog : DialogFragment() {
@ -16,19 +12,8 @@ class ChangeUiModeDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext() val context = requireContext()
return android.app.AlertDialog.Builder(context)
val displayNameList = UiMode.values().map { getString(it.displayNameRes) }.toTypedArray() .setTitle("TODO: remove this")
val activeUiMode = UiModeUtilities.getUserSelectedUiMode(context) .show()
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()
} }
} }

View File

@ -3,14 +3,13 @@ package org.thoughtcrime.securesms.preferences
import android.os.Bundle import android.os.Bundle
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment
class ChatSettingsActivity : PassphraseRequiredActionBarActivity() { class ChatSettingsActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_fragment_wrapper) 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 fragment = ChatsPreferenceFragment()
val transaction = supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment) transaction.replace(R.id.fragmentContainer, fragment)

View File

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

View File

@ -2,9 +2,18 @@ package org.thoughtcrime.securesms.preferences;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle; 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.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
@ -12,19 +21,30 @@ import androidx.preference.PreferenceGroupAdapter;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView; 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.components.CustomDefaultPreference;
import org.thoughtcrime.securesms.conversation.v2.ViewUtil;
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference; import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat; import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat;
import network.loki.messenger.R;
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat { 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 @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
horizontalPadding = ViewUtil.dpToPx(requireContext(), 36);
verticalPadding = ViewUtil.dpToPx(requireContext(), 8);
} }
@Override @Override
@ -33,6 +53,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
View lv = getView().findViewById(android.R.id.list); View lv = getView().findViewById(android.R.id.list);
if (lv != null) lv.setPadding(0, 0, 0, 0); if (lv != null) lv.setPadding(0, 0, 0, 0);
setDivider(null);
} }
@Override @Override
@ -57,17 +78,100 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
return new PreferenceGroupAdapter(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 @Override
public void onBindViewHolder(PreferenceViewHolder holder, int position) { public void onBindViewHolder(PreferenceViewHolder holder, int position) {
super.onBindViewHolder(holder, position); super.onBindViewHolder(holder, position);
Preference preference = getItem(position); Preference preference = getItem(position);
if (preference instanceof PreferenceCategory) { 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); setZeroPaddingToLayoutChildren(holder.itemView);
} else { } else {
View iconFrame = holder.itemView.findViewById(R.id.icon_frame); View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
if (iconFrame != null) { if (iconFrame != null) {
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE); iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
} }
Drawable background = getBackground(holder.itemView.getContext(), position);
holder.itemView.setBackground(background);
TextView titleView = holder.itemView.findViewById(android.R.id.title);
if (titleView != null) {
((TextView) titleView).setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
}
boolean isTop = isTop(position);
boolean isBottom = isBottom(position);
holder.itemView.setPadding(horizontalPadding, isTop ? verticalPadding : 0, horizontalPadding, isBottom ? verticalPadding : 0);
} }
} }
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import android.provider.Settings;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.Preference; 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.CallNotificationBuilder;
import org.thoughtcrime.securesms.util.IntentUtils; import org.thoughtcrime.securesms.util.IntentUtils;
import java.util.concurrent.TimeUnit;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function1;
import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
import network.loki.messenger.BuildConfig; import network.loki.messenger.BuildConfig;
import network.loki.messenger.R; import network.loki.messenger.R;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment { public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFragment {
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
@ -42,7 +40,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
super.onCreate(paramBundle); super.onCreate(paramBundle);
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener()); 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.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener()); 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); ((SwitchPreferenceCompat)findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)).setChecked(isEnabled);
if (isEnabled && !CallNotificationBuilder.areNotificationsEnabled(requireActivity())) { 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 // 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) .setTitle(R.string.CallNotificationBuilder_system_notification_title)
.setMessage(R.string.CallNotificationBuilder_system_notification_message) .setMessage(R.string.CallNotificationBuilder_system_notification_message)
.setPositiveButton(R.string.activity_notification_settings_title, (d, w) -> { .setPositiveButton(R.string.activity_notification_settings_title, (d, w) -> {
@ -100,20 +97,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
@Override @Override
public void onResume() { public void onResume() {
super.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() { 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 { private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -215,20 +179,16 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
boolean val = (boolean) newValue; boolean val = (boolean) newValue;
if (val) { if (val) {
// check if we've shown the info dialog and check for microphone permissions // check if we've shown the info dialog and check for microphone permissions
if (TextSecurePreferences.setShownCallWarning(context.requireContext())) { new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog))
new AlertDialog.Builder(context.requireContext()) .setTitle(R.string.dialog_voice_video_title)
.setTitle(R.string.dialog_voice_video_title) .setMessage(R.string.dialog_voice_video_message)
.setMessage(R.string.dialog_voice_video_message) .setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> { requestMicrophonePermission();
requestMicrophonePermission(); })
}) .setNegativeButton(R.string.cancel, (d, w) -> {
.setNegativeButton(R.string.cancel, (d, w) -> {
}) })
.show(); .show();
} else {
requestMicrophonePermission();
}
return false; return false;
} else { } else {
return true; return true;

View File

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

View File

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

View File

@ -8,10 +8,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Parcelable
import android.util.SparseArray
import android.view.ActionMode import android.view.ActionMode
import android.view.Menu import android.view.Menu
import android.view.MenuItem 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.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.show
@ -67,6 +68,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
companion object { companion object {
const val updatedProfileResultCode = 1234 const val updatedProfileResultCode = 1234
private const val SCROLL_STATE = "SCROLL_STATE"
} }
// region Lifecycle // region Lifecycle
@ -94,24 +96,31 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
notificationsButton.setOnClickListener { showNotificationSettings() } notificationsButton.setOnClickListener { showNotificationSettings() }
messageRequestsButton.setOnClickListener { showMessageRequests() } messageRequestsButton.setOnClickListener { showMessageRequests() }
chatsButton.setOnClickListener { showChatSettings() } chatsButton.setOnClickListener { showChatSettings() }
sendInvitationButton.setOnClickListener { sendInvitation() } appearanceButton.setOnClickListener { showAppearanceSettings() }
faqButton.setOnClickListener { showFAQ() } inviteFriendButton.setOnClickListener { sendInvitation() }
surveyButton.setOnClickListener { showSurvey() } helpButton.setOnClickListener { showHelp() }
helpTranslateButton.setOnClickListener { helpTranslate() }
seedButton.setOnClickListener { showSeed() } seedButton.setOnClickListener { showSeed() }
clearAllDataButton.setOnClickListener { clearAllData() } 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})") 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.settings_general, menu) 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 return true
} }
@ -121,10 +130,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
showQRCode() showQRCode()
true true
} }
R.id.action_change_theme -> {
ChangeUiModeDialog().show(supportFragmentManager, ChangeUiModeDialog.TAG)
true
}
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
@ -295,6 +300,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
push(intent) push(intent)
} }
private fun showAppearanceSettings() {
val intent = Intent(this, AppearanceSettingsActivity::class.java)
push(intent)
}
private fun sendInvitation() { private fun sendInvitation() {
val intent = Intent() val intent = Intent()
intent.action = Intent.ACTION_SEND intent.action = Intent.ACTION_SEND
@ -305,14 +315,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
startActivity(chooser) startActivity(chooser)
} }
private fun showFAQ() { private fun showHelp() {
try { val intent = Intent(this, HelpSettingsActivity::class.java)
val url = "https://getsession.org/faq" push(intent)
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 showPath() { private fun showPath() {
@ -320,26 +325,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
show(intent) 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() { private fun showSeed() {
SeedDialog().show(supportFragmentManager, "Recovery Phrase Dialog") SeedDialog().show(supportFragmentManager, "Recovery Phrase Dialog")
} }
@ -348,20 +333,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") 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 // endregion
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback { private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager; 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.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.shape.CornerFamily; import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel; import com.google.android.material.shape.ShapeAppearanceModel;
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener; import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
@ -80,7 +77,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.Widget_TextSecure_ReactWithAny);
} }
@Override @Override
@ -93,22 +89,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
.setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18)) .setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18))
.build(); .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); boolean shadows = requireArguments().getBoolean(ARG_SHADOWS, true);
if (!shadows) { if (!shadows) {
Window window = dialog.getWindow(); Window window = dialog.getWindow();

View File

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

View File

@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.util
import android.animation.ArgbEvaluator import android.animation.ArgbEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.* import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
@ -11,8 +12,6 @@ import android.widget.RelativeLayout
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toPx
import kotlin.math.roundToInt import kotlin.math.roundToInt
interface GlowView { interface GlowView {
@ -22,7 +21,7 @@ interface GlowView {
object GlowViewUtilities { 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 startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme) val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
@ -34,7 +33,17 @@ object GlowViewUtilities {
animation.start() 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 startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme) val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
@ -45,6 +54,17 @@ object GlowViewUtilities {
} }
animation.start() 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 { class PNModeView : LinearLayout, GlowView {

View File

@ -2,46 +2,12 @@ package org.thoughtcrime.securesms.util
import android.content.Context import android.content.Context
import android.content.res.Configuration 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. * Day/night UI mode related utilities.
* @see <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme">Official Documentation</a> * @see <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme">Official Documentation</a>
*/ */
object UiModeUtilities { 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 * 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 val uiModeNightBit = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return uiModeNightBit == Configuration.UI_MODE_NIGHT_NO return uiModeNightBit == Configuration.UI_MODE_NIGHT_NO
} }
}
enum class UiMode(
@StringRes
val displayNameRes: Int,
val nightModeValue: Int) {
DAY(R.string.dialog_ui_mode_option_day, AppCompatDelegate.MODE_NIGHT_NO),
NIGHT(R.string.dialog_ui_mode_option_night, AppCompatDelegate.MODE_NIGHT_YES),
SYSTEM_DEFAULT(R.string.dialog_ui_mode_option_system_default, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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