mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
New app theming (#913)
* feat: start new app theming feature * feat: add some theming colours * refactor: start refactoring themes and colours to use dynamic attributes * feat: adding more colours and switching over default colours to be theme based instead of hard-coded or day/night specific * refactor: take a look at ocean light and logo colour * feat: global search colours for light and dark ocean * feat: more styling * feat: adding themes to conversation activity and refactoring the base theme to apply over the top of the activity's theme so it retains noActionBar etc * feat: add dynamic accent color * docs: add todo for changing how accent colour is applied * feat: update new theming to use override primary style so that the regular colorAccent attribute can be used in existing layouts * feat: coordinating styles across layouts, fixing up pinned icons and naming for conversation list items * refactor: re-styling layouts to match new themes and attributes. Need to figure out action mode close button * refactor: remove @color/text and replace with ?android:textColorPrimary to override in themes * refactor: add context theme wrapper to bottom sheet dialog that references accent color * fix: input bar bug fix and preference activity themes * refactor: new settings menu options * fix: crash for PNModeActivity.kt refactor: move ordering in seed dialog to match designs, copy changes to match new settings menu * feat: add new appearance settings activity * refactor: title and VM changes * fix: correct override * feat: add theme appearance screen UI features and start VM implementation. re-add legacy theme utils to get default for migration * fix: compile errors and missing themes from emoji features * refactor: remove background shape alteration and old bottom sheet styles, re-add the theme mode attr * feat: appearance screen wired up, just need to refresh theme * feat: add theme state recreation and fix match system settings option * refactor: add bottom margin * feat: explore custom preference category * feat: add the customized session theme for CorrectedPreferenceFragment * feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment * refactor: change drawable style and remove explicit dividers * refactor: remove divider in CorrectedPreferenceFragment * feat: add theme state check on resume, might be jarring currently * feat: add preference divider elements for settings menu * refactor: settings menu redesigns * refactor: change led preference to integer and refactor TextSecurePreferences.kt * feat: add scroll parcel to save/restore hierarchy on restart with appearance changes * feat: add the conversations blocked contacts and refactor preference order and copy * feat: add blocked contacts activity, basic layout and vm * feat: add unblock DB functions and storage protocol, start working on the DB query state flow, might have to just implement recipient on modified listener * feat: add blocked contacts and notif recipient listeners * feat: add recipient db reader * feat: add blocked contact interactions and fix a theming crash for notifications * feat: introduce better equals and hashcode implementations to recipient, replace home diff util content check with hashcode-based comparison * feat: add settings menu vectors * fix: preview compile error * refactor: migrating settings menu to new designs * feat: help menu * refactor: simplify link opening * refactor: remove space * feat: refactor preferences and start theming for light mode options * refactor: fixing dark and light modes with dialogs * refactor: popup dialogs use proper themes now * refactor: alert dialogs and media edit fragments use attribute references * refactor: use input bar button attribute instead color control normal in vector tint * refactor: transparency, dialog fixes, notification fix * refactor: attrs and styles for buttons * fix: use prominent button color on the outline button's border * fix: fix the trash * refactor: remove the appearance * refactor: avatar placeholder generation, chips and element border styles * refactor: use colors instead of style references * refactor: theming changes to match designs and feedback * refactor: the titles are bold and the categories are tertiary coloured now * fix: appearance settings match preferences, search bottom bar uses themed attributes * refactor: increase setting button height * Update clear all data dialog * Update seed dialog * refactor: more qa feedback changes * feat: add new TLs and fa-rIR TLs * Update notification content dialog * Fix message requests clear all button text color * feat: re-add screenshot observer * refactor: make send tint accent color * feat: add unread background differences * fix: change unread count indicator * build: upgrade build numbers * Fix message requests popupmenu background color * fix: crash from attr reference in color attribute * build: upgrade build number * fix: message bubbles, thumbnail backgrounds, search bar visibility with input bar, attachment buttons * fix: tertiary text for keyboard page search view * fix: emoji overflow colour differences * fix: reaction pill dialog background is now correct colour * Add style to reactions tab layout * fix: appearance activity reverting primary color at correct time * fix: show call privacy warning every time instead of just once * fix: gradient background(?) and audio autoplay disable * fix: crash in all media containing documents * fix: reaction dialog heading fixes * Add style to reactions tab layout * fix: remove gradient backgrounds * fix: adding new reaction normal text attribute to try correct the tab layout * fix: ocean dark unread/read colours * build; update build number * build: update build number Co-authored-by: charles <charles@oxen.io>
This commit is contained in:
parent
92075aed32
commit
7a773016da
@ -27,7 +27,7 @@ configurations.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'com.google.android:flexbox:2.0.1'
|
||||
@ -157,8 +157,8 @@ dependencies {
|
||||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 303
|
||||
def canonicalVersionName = "1.15.4"
|
||||
def canonicalVersionCode = 307
|
||||
def canonicalVersionName = "1.16.0"
|
||||
|
||||
def postFixSize = 10
|
||||
def abiPostFix = ['armeabi-v7a' : 1,
|
||||
|
@ -140,6 +140,12 @@
|
||||
android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.preferences.BlockedContactsActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar"
|
||||
android:label="@string/blocked_contacts_title"
|
||||
/>
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.groups.EditClosedGroupActivity"
|
||||
android:label="@string/activity_edit_closed_group_title"
|
||||
@ -160,6 +166,12 @@
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.preferences.ChatSettingsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.preferences.HelpSettingsActivity"
|
||||
android:label="@string/activity_help_settings_title"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity android:name="org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity"
|
||||
android:screenOrientation="portrait"/>
|
||||
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.ShareActivity"
|
||||
@ -216,12 +228,12 @@
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.TextSecure.DayNight">
|
||||
android:theme="@style/Theme.Session.DayNight">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.longmessage.LongMessageActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.TextSecure.DayNight" />
|
||||
android:theme="@style/Theme.Session.DayNight" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.DatabaseUpgradeActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
@ -235,14 +247,14 @@
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.giph.ui.GiphyActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
||||
android:theme="@style/Theme.Session.DayNight.NoActionBar"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.mediasend.MediaSendActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
||||
android:theme="@style/Theme.Session.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.MediaPreviewActivity"
|
||||
@ -381,6 +393,10 @@
|
||||
android:name="org.thoughtcrime.securesms.database.DatabaseContentProviders$StickerPack"
|
||||
android:authorities="network.loki.securesms.database.stickerpack"
|
||||
android:exported="false" />
|
||||
<provider
|
||||
android:name="org.thoughtcrime.securesms.database.DatabaseContentProviders$Recipient"
|
||||
android:authorities="network.loki.securesms.database.recipient"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver">
|
||||
<intent-filter>
|
||||
|
@ -85,7 +85,6 @@ import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
@ -165,6 +164,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
return (ApplicationContext) context.getApplicationContext();
|
||||
}
|
||||
|
||||
public TextSecurePreferences getPrefs() {
|
||||
return textSecurePreferences;
|
||||
}
|
||||
|
||||
public DatabaseComponent getDatabaseComponent() {
|
||||
return EntryPoints.get(getApplicationContext(), DatabaseComponent.class);
|
||||
}
|
||||
@ -220,7 +223,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
if (userPublicKey != null) {
|
||||
registerForFCMIfNeeded(false);
|
||||
}
|
||||
UiModeUtilities.setupUiModeToUserSelected(this);
|
||||
initializeExpiringMessageManager();
|
||||
initializeTypingStatusRepository();
|
||||
initializeTypingStatusSender();
|
||||
|
@ -1,44 +1,103 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import static org.session.libsession.utilities.TextSecurePreferences.SELECTED_ACCENT_COLOR;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
|
||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
|
||||
import org.thoughtcrime.securesms.util.ThemeState;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||
private ThemeState currentThemeState;
|
||||
|
||||
private TextSecurePreferences getPreferences() {
|
||||
ApplicationContext appContext = (ApplicationContext) getApplicationContext();
|
||||
return appContext.textSecurePreferences;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDesiredTheme() {
|
||||
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
|
||||
int userSelectedTheme = themeState.getTheme();
|
||||
if (themeState.getFollowSystem()) {
|
||||
// do light or dark based on the selected theme
|
||||
boolean isDayUi = UiModeUtilities.isDayUiMode(this);
|
||||
if (userSelectedTheme == R.style.Ocean_Dark || userSelectedTheme == R.style.Ocean_Light) {
|
||||
return isDayUi ? R.style.Ocean_Light : R.style.Ocean_Dark;
|
||||
} else {
|
||||
return isDayUi ? R.style.Classic_Light : R.style.Classic_Dark;
|
||||
}
|
||||
} else {
|
||||
return userSelectedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
@StyleRes @Nullable
|
||||
public Integer getAccentTheme() {
|
||||
if (!getPreferences().hasPreference(SELECTED_ACCENT_COLOR)) return null;
|
||||
ThemeState themeState = ActivityUtilitiesKt.themeState(getPreferences());
|
||||
return themeState.getAccentStyle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources.Theme getTheme() {
|
||||
// New themes
|
||||
Resources.Theme modifiedTheme = super.getTheme();
|
||||
modifiedTheme.applyStyle(getDesiredTheme(), true);
|
||||
Integer accentTheme = getAccentTheme();
|
||||
if (accentTheme != null) {
|
||||
modifiedTheme.applyStyle(accentTheme, true);
|
||||
}
|
||||
currentThemeState = ActivityUtilitiesKt.themeState(getPreferences());
|
||||
return modifiedTheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initializeScreenshotSecurity();
|
||||
initializeScreenshotSecurity(true);
|
||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
||||
String name = getResources().getString(R.string.app_name);
|
||||
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
||||
int color = getResources().getColor(R.color.app_icon_background);
|
||||
setTaskDescription(new ActivityManager.TaskDescription(name, icon, color));
|
||||
if (!currentThemeState.equals(ActivityUtilitiesKt.themeState(getPreferences()))) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
initializeScreenshotSecurity(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,11 +108,15 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initializeScreenshotSecurity() {
|
||||
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||
private void initializeScreenshotSecurity(boolean isResume) {
|
||||
if (!isResume) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
package org.thoughtcrime.securesms.attachments
|
||||
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
class ScreenshotObserver(private val context: Context, handler: Handler, private val screenshotTriggered: ()->Unit): ContentObserver(handler) {
|
||||
|
||||
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||
super.onChange(selfChange, uri)
|
||||
uri ?: return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
queryRelativeDataColumn(uri)
|
||||
} else {
|
||||
queryDataColumn(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private val cache = mutableSetOf<Int>()
|
||||
|
||||
private fun queryDataColumn(uri: Uri) {
|
||||
val projection = arrayOf(
|
||||
MediaStore.Images.Media.DATA
|
||||
)
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)?.use { cursor ->
|
||||
val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
|
||||
while (cursor.moveToNext()) {
|
||||
val path = cursor.getString(dataColumn)
|
||||
if (path.contains("screenshot", true)) {
|
||||
if (cache.add(uri.hashCode())) {
|
||||
screenshotTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun queryRelativeDataColumn(uri: Uri) {
|
||||
val projection = arrayOf(
|
||||
MediaStore.Images.Media.DISPLAY_NAME,
|
||||
MediaStore.Images.Media.RELATIVE_PATH
|
||||
)
|
||||
context.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)?.use { cursor ->
|
||||
val relativePathColumn =
|
||||
cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
|
||||
val displayNameColumn =
|
||||
cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
|
||||
while (cursor.moveToNext()) {
|
||||
val name = cursor.getString(displayNameColumn)
|
||||
val relativePath = cursor.getString(relativePathColumn)
|
||||
if (name.contains("screenshot", true) or
|
||||
relativePath.contains("screenshot", true)) {
|
||||
if (cache.add(uri.hashCode())) {
|
||||
screenshotTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -107,7 +107,7 @@ class ProfilePictureView @JvmOverloads constructor(
|
||||
if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return
|
||||
val signalProfilePicture = recipient.contactPhoto
|
||||
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
|
||||
val placeholder = PlaceholderAvatarPhoto(publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
|
||||
val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
|
||||
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
|
||||
glide.clear(imageView)
|
||||
glide.load(signalProfilePicture)
|
||||
|
@ -1,304 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.session.libsession.messaging.contacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsession.utilities.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class QuoteView extends FrameLayout implements RecipientModifiedListener {
|
||||
|
||||
private static final String TAG = QuoteView.class.getSimpleName();
|
||||
|
||||
private static final int MESSAGE_TYPE_PREVIEW = 0;
|
||||
private static final int MESSAGE_TYPE_OUTGOING = 1;
|
||||
private static final int MESSAGE_TYPE_INCOMING = 2;
|
||||
|
||||
private ViewGroup mainView;
|
||||
private ViewGroup footerView;
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private ImageView quoteBarView;
|
||||
private ImageView thumbnailView;
|
||||
private View attachmentVideoOverlayView;
|
||||
private ViewGroup attachmentContainerView;
|
||||
private TextView attachmentNameView;
|
||||
private ImageView dismissView;
|
||||
|
||||
private long id;
|
||||
private Recipient author;
|
||||
private String body;
|
||||
private Recipient conversationRecipient;
|
||||
private TextView mediaDescriptionText;
|
||||
private TextView missingLinkText;
|
||||
private SlideDeck attachments;
|
||||
private int messageType;
|
||||
private int largeCornerRadius;
|
||||
private int smallCornerRadius;
|
||||
private CornerMask cornerMask;
|
||||
|
||||
|
||||
public QuoteView(Context context) {
|
||||
super(context);
|
||||
initialize(null);
|
||||
}
|
||||
|
||||
public QuoteView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public QuoteView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
private void initialize(@Nullable AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.quote_view, this);
|
||||
|
||||
this.mainView = findViewById(R.id.quote_main);
|
||||
this.footerView = findViewById(R.id.quote_missing_footer);
|
||||
this.authorView = findViewById(R.id.quote_author);
|
||||
this.bodyView = findViewById(R.id.quote_text);
|
||||
this.quoteBarView = findViewById(R.id.quote_bar);
|
||||
this.thumbnailView = findViewById(R.id.quote_thumbnail);
|
||||
this.attachmentVideoOverlayView = findViewById(R.id.quote_video_overlay);
|
||||
this.attachmentContainerView = findViewById(R.id.quote_attachment_container);
|
||||
this.attachmentNameView = findViewById(R.id.quote_attachment_name);
|
||||
this.dismissView = findViewById(R.id.quote_dismiss);
|
||||
this.mediaDescriptionText = findViewById(R.id.media_type);
|
||||
this.missingLinkText = findViewById(R.id.quote_missing_text);
|
||||
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
|
||||
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
|
||||
|
||||
cornerMask = new CornerMask(this);
|
||||
cornerMask.setRadii(largeCornerRadius, largeCornerRadius, smallCornerRadius, smallCornerRadius);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
|
||||
int primaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorPrimary, Color.BLACK);
|
||||
int secondaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorSecondary, Color.BLACK);
|
||||
messageType = typedArray.getInt(R.styleable.QuoteView_message_type, 0);
|
||||
typedArray.recycle();
|
||||
|
||||
dismissView.setVisibility(messageType == MESSAGE_TYPE_PREVIEW ? VISIBLE : GONE);
|
||||
|
||||
authorView.setTextColor(primaryColor);
|
||||
bodyView.setTextColor(primaryColor);
|
||||
attachmentNameView.setTextColor(primaryColor);
|
||||
mediaDescriptionText.setTextColor(secondaryColor);
|
||||
missingLinkText.setTextColor(primaryColor);
|
||||
|
||||
if (messageType == MESSAGE_TYPE_PREVIEW) {
|
||||
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
|
||||
cornerMask.setTopLeftRadius(radius);
|
||||
cornerMask.setTopRightRadius(radius);
|
||||
}
|
||||
}
|
||||
|
||||
dismissView.setOnClickListener(view -> setVisibility(GONE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
cornerMask.mask(canvas);
|
||||
}
|
||||
|
||||
public void setQuote(GlideRequests glideRequests,
|
||||
long id,
|
||||
@NonNull Recipient author,
|
||||
@Nullable String body,
|
||||
boolean originalMissing,
|
||||
@NonNull SlideDeck attachments,
|
||||
@NonNull Recipient conversationRecipient)
|
||||
{
|
||||
if (this.author != null) this.author.removeListener(this);
|
||||
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.body = body;
|
||||
this.attachments = attachments;
|
||||
this.conversationRecipient = conversationRecipient;
|
||||
|
||||
author.addListener(this);
|
||||
setQuoteAuthor(author);
|
||||
setQuoteText(body, attachments);
|
||||
setQuoteAttachment(glideRequests, attachments);
|
||||
setQuoteMissingFooter(originalMissing);
|
||||
}
|
||||
|
||||
public void setTopCornerSizes(boolean topLeftLarge, boolean topRightLarge) {
|
||||
cornerMask.setTopLeftRadius(topLeftLarge ? largeCornerRadius : smallCornerRadius);
|
||||
cornerMask.setTopRightRadius(topRightLarge ? largeCornerRadius : smallCornerRadius);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
if (this.author != null) this.author.removeListener(this);
|
||||
|
||||
this.id = 0;
|
||||
this.author = null;
|
||||
this.body = null;
|
||||
|
||||
setVisibility(GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
Util.runOnMain(() -> {
|
||||
if (recipient == author) {
|
||||
setQuoteAuthor(recipient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setQuoteAuthor(@NonNull Recipient author) {
|
||||
boolean outgoing = messageType != MESSAGE_TYPE_INCOMING;
|
||||
boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress().serialize());
|
||||
|
||||
String quoteeDisplayName;
|
||||
|
||||
String senderHexEncodedPublicKey = author.getAddress().serialize();
|
||||
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
|
||||
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
|
||||
} else {
|
||||
SessionContactDatabase contactDB = DatabaseComponent.get(getContext()).sessionContactDatabase();
|
||||
Contact contact = contactDB.getContactWithSessionID(senderHexEncodedPublicKey);
|
||||
if (contact != null) {
|
||||
Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR;
|
||||
quoteeDisplayName = contact.displayName(context);
|
||||
} else {
|
||||
quoteeDisplayName = senderHexEncodedPublicKey;
|
||||
}
|
||||
}
|
||||
|
||||
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName);
|
||||
|
||||
// We use the raw color resource because Android 4.x was struggling with tints here
|
||||
int colorID = UiModeUtilities.isDayUiMode(getContext()) ? R.color.black : R.color.accent;
|
||||
quoteBarView.setImageResource(colorID);
|
||||
mainView.setBackgroundColor(ThemeUtil.getThemedColor(getContext(),
|
||||
outgoing ? R.attr.message_received_background_color : R.attr.message_sent_background_color));
|
||||
}
|
||||
|
||||
private void setQuoteText(@Nullable String body, @NonNull SlideDeck attachments) {
|
||||
if (!TextUtils.isEmpty(body) || !attachments.containsMediaSlide()) {
|
||||
bodyView.setVisibility(VISIBLE);
|
||||
bodyView.setText(body == null ? "" : body);
|
||||
mediaDescriptionText.setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
bodyView.setVisibility(GONE);
|
||||
mediaDescriptionText.setVisibility(VISIBLE);
|
||||
|
||||
List<Slide> audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
List<Slide> imageSlides = Stream.of(attachments.getSlides()).filter(Slide::hasImage).limit(1).toList();
|
||||
List<Slide> videoSlides = Stream.of(attachments.getSlides()).filter(Slide::hasVideo).limit(1).toList();
|
||||
|
||||
// Given that most types have images, we specifically check images last
|
||||
if (!audioSlides.isEmpty()) {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_audio);
|
||||
} else if (!documentSlides.isEmpty()) {
|
||||
mediaDescriptionText.setVisibility(GONE);
|
||||
} else if (!videoSlides.isEmpty()) {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_video);
|
||||
} else if (!imageSlides.isEmpty()) {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_photo);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
|
||||
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
|
||||
attachmentVideoOverlayView.setVisibility(GONE);
|
||||
|
||||
if (!imageVideoSlides.isEmpty() && imageVideoSlides.get(0).getThumbnailUri() != null) {
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
attachmentContainerView.setVisibility(GONE);
|
||||
dismissView.setBackgroundResource(R.drawable.dismiss_background);
|
||||
if (imageVideoSlides.get(0).hasVideo()) {
|
||||
attachmentVideoOverlayView.setVisibility(VISIBLE);
|
||||
}
|
||||
glideRequests.load(new DecryptableUri(imageVideoSlides.get(0).getThumbnailUri()))
|
||||
.centerCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(thumbnailView);
|
||||
} else if (!documentSlides.isEmpty()){
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(VISIBLE);
|
||||
attachmentNameView.setText(documentSlides.get(0).getFileName().or(""));
|
||||
} else {
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(GONE);
|
||||
dismissView.setBackgroundDrawable(null);
|
||||
}
|
||||
|
||||
if (ThemeUtil.isDarkTheme(getContext())) {
|
||||
dismissView.setBackgroundResource(R.drawable.circle_alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuoteMissingFooter(boolean missing) {
|
||||
footerView.setVisibility(missing ? VISIBLE : GONE);
|
||||
footerView.setBackgroundColor(getResources().getColor(R.color.quote_not_found_background));
|
||||
}
|
||||
|
||||
public long getQuoteId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Recipient getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments.asAttachments();
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.CheckBoxPreference;
|
||||
import androidx.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
@ -18,7 +17,6 @@ public class SwitchPreferenceCompat extends CheckBoxPreference {
|
||||
setLayoutRes();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public SwitchPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setLayoutRes();
|
||||
|
@ -68,18 +68,22 @@ class UserView : LinearLayout {
|
||||
}
|
||||
ActionIndicator.Tick -> {
|
||||
binding.actionIndicatorImageView.visibility = View.VISIBLE
|
||||
binding.actionIndicatorImageView.setImageResource(
|
||||
if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle
|
||||
)
|
||||
if (isSelected) {
|
||||
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
|
||||
} else {
|
||||
binding.actionIndicatorImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleCheckbox(isSelected: Boolean = false) {
|
||||
binding.actionIndicatorImageView.visibility = View.VISIBLE
|
||||
binding.actionIndicatorImageView.setImageResource(
|
||||
if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle
|
||||
)
|
||||
if (isSelected) {
|
||||
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
|
||||
} else {
|
||||
binding.actionIndicatorImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
|
@ -41,7 +41,7 @@ class NewConversationFragment : BottomSheetDialogFragment(), NewConversationDele
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = BottomSheetDialog(requireContext(), theme)
|
||||
val dialog = BottomSheetDialog(requireContext(), R.style.Theme_Session_BottomSheet)
|
||||
dialog.setOnShowListener {
|
||||
val bottomSheetDialog = it as BottomSheetDialog
|
||||
val parentLayout =
|
||||
|
@ -4,8 +4,11 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.FragmentNewConversationHomeBinding
|
||||
@ -57,5 +60,11 @@ class NewConversationHomeFragment : Fragment() {
|
||||
contactGroups.remove(unknownSectionTitle)?.let { contactGroups.put(unknownSectionTitle, it) }
|
||||
adapter.items = contactGroups.flatMap { entry -> listOf(ContactListItem.Header(entry.key)) + entry.value }
|
||||
binding.contactsRecyclerView.adapter = adapter
|
||||
val divider = ContextCompat.getDrawable(requireActivity(), R.drawable.conversation_menu_divider)!!.let {
|
||||
DividerItemDecoration(requireActivity(), RecyclerView.VERTICAL).apply {
|
||||
setDrawable(it)
|
||||
}
|
||||
}
|
||||
binding.contactsRecyclerView.addItemDecoration(divider)
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.MediaStore
|
||||
import android.text.TextUtils
|
||||
import android.util.Pair
|
||||
import android.util.TypedValue
|
||||
@ -85,6 +86,7 @@ import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.ExpirationDialog
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.attachments.ScreenshotObserver
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
||||
@ -191,6 +193,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
@Inject lateinit var reactionDb: ReactionDatabase
|
||||
@Inject lateinit var viewModelFactory: ConversationViewModel.AssistedFactory
|
||||
|
||||
private val screenshotObserver by lazy {
|
||||
ScreenshotObserver(this, Handler(Looper.getMainLooper())) {
|
||||
// post screenshot message
|
||||
sendScreenshotNotification()
|
||||
}
|
||||
}
|
||||
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private val linkPreviewViewModel: LinkPreviewViewModel by lazy {
|
||||
ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository()))
|
||||
@ -380,11 +389,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId)
|
||||
val recipient = viewModel.recipient ?: return
|
||||
threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient)
|
||||
contentResolver.registerContentObserver(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
true,
|
||||
screenshotObserver
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1)
|
||||
contentResolver.unregisterContentObserver(screenshotObserver)
|
||||
}
|
||||
|
||||
override fun getSystemService(name: String): Any? {
|
||||
@ -923,10 +938,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
} else if (recipient.isGroupRecipient) {
|
||||
viewModel.openGroup?.let { openGroup ->
|
||||
val userCount = lokiApiDb.getUserCount(openGroup.room, openGroup.server) ?: 0
|
||||
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_member_count, userCount)
|
||||
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_active_member_count, userCount)
|
||||
} ?: run {
|
||||
actionBarBinding.conversationSubtitleView.isVisible = false
|
||||
val userCount = groupDb.getGroupMemberAddresses(recipient.address.toGroupString(), true).size
|
||||
actionBarBinding.conversationSubtitleView.text = getString(R.string.ConversationActivity_member_count, userCount)
|
||||
}
|
||||
viewModel
|
||||
} else {
|
||||
actionBarBinding.conversationSubtitleView.isVisible = false
|
||||
}
|
||||
@ -1317,6 +1334,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
|
||||
override fun playVoiceMessageAtIndexIfPossible(indexInAdapter: Int) {
|
||||
if (!textSecurePreferences.autoplayAudioMessages()) return
|
||||
|
||||
if (indexInAdapter < 0 || indexInAdapter >= adapter.itemCount) { return }
|
||||
val viewHolder = binding?.conversationRecyclerView?.findViewHolderForAdapterPosition(indexInAdapter) as? ConversationAdapter.VisibleMessageViewHolder ?: return
|
||||
val visibleMessageView = ViewVisibleMessageBinding.bind(viewHolder.view).visibleMessageView
|
||||
@ -1770,6 +1789,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
endActionMode()
|
||||
}
|
||||
|
||||
private fun sendScreenshotNotification() {
|
||||
val recipient = viewModel.recipient ?: return
|
||||
if (recipient.isGroupRecipient) return
|
||||
val kind = DataExtractionNotification.Kind.Screenshot()
|
||||
val message = DataExtractionNotification(kind)
|
||||
MessageSender.send(message, recipient.address)
|
||||
}
|
||||
|
||||
private fun sendMediaSavedNotification() {
|
||||
val recipient = viewModel.recipient ?: return
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
@ -1819,7 +1846,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
searchViewModel.onSearchOpened()
|
||||
binding?.searchBottomBar?.visibility = View.VISIBLE
|
||||
binding?.searchBottomBar?.setData(0, 0)
|
||||
binding?.inputBar?.visibility = View.GONE
|
||||
binding?.inputBar?.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
fun onSearchClosed() {
|
||||
@ -1873,6 +1900,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems)
|
||||
ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(selectedItems)
|
||||
ConversationReactionOverlay.Action.BAN_USER -> banUser(selectedItems)
|
||||
ConversationReactionOverlay.Action.COPY_SESSION_ID -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,12 @@ import android.net.Uri
|
||||
import android.text.InputType
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewInputBarBinding
|
||||
@ -27,7 +30,8 @@ import org.thoughtcrime.securesms.util.contains
|
||||
import org.thoughtcrime.securesms.util.toDp
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
|
||||
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate {
|
||||
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate,
|
||||
TextView.OnEditorActionListener {
|
||||
private lateinit var binding: ViewInputBarBinding
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private val vMargin by lazy { toDp(4, resources) }
|
||||
@ -85,11 +89,31 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
|
||||
}
|
||||
}
|
||||
// Edit text
|
||||
binding.inputBarEditText.setOnEditorActionListener(this)
|
||||
if (TextSecurePreferences.isEnterSendsEnabled(context)) {
|
||||
binding.inputBarEditText.imeOptions = EditorInfo.IME_ACTION_SEND
|
||||
binding.inputBarEditText.inputType =
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
} else {
|
||||
binding.inputBarEditText.imeOptions = EditorInfo.IME_ACTION_NONE
|
||||
binding.inputBarEditText.inputType =
|
||||
binding.inputBarEditText.inputType or
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
}
|
||||
val incognitoFlag = if (TextSecurePreferences.isIncognitoKeyboardEnabled(context)) 16777216 else 0
|
||||
binding.inputBarEditText.imeOptions = binding.inputBarEditText.imeOptions or incognitoFlag // Always use incognito keyboard if setting enabled
|
||||
binding.inputBarEditText.inputType = binding.inputBarEditText.inputType or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
binding.inputBarEditText.delegate = this
|
||||
}
|
||||
|
||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||
if (v === binding.inputBarEditText && actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
// same as pressing send button
|
||||
delegate?.sendMessage()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
|
@ -16,8 +16,13 @@ import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.DrawableRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.util.*
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.InputBarButtonImageViewContainer
|
||||
import org.thoughtcrime.securesms.util.animateSizeChange
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import java.util.Date
|
||||
|
||||
class InputBarButton : RelativeLayout {
|
||||
private val gestureHandler = Handler(Looper.getMainLooper())
|
||||
@ -43,11 +48,11 @@ class InputBarButton : RelativeLayout {
|
||||
private val collapsedImageViewPosition by lazy { PointF((expandedSize - collapsedSize) / 2, (expandedSize - collapsedSize) / 2) }
|
||||
private val colorID by lazy {
|
||||
if (hasOpaqueBackground) {
|
||||
R.color.input_bar_button_background_opaque
|
||||
R.attr.input_bar_button_background_opaque
|
||||
} else if (isSendButton) {
|
||||
R.color.accent
|
||||
R.attr.colorAccent
|
||||
} else {
|
||||
R.color.input_bar_button_background
|
||||
R.attr.input_bar_button_background
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,9 +64,9 @@ class InputBarButton : RelativeLayout {
|
||||
val size = collapsedSize.toInt()
|
||||
result.layoutParams = LayoutParams(size, size)
|
||||
result.setBackgroundResource(R.drawable.input_bar_button_background)
|
||||
result.mainColor = resources.getColorWithID(colorID, context.theme)
|
||||
result.mainColor = context.getColorFromAttr(colorID)
|
||||
if (hasOpaqueBackground) {
|
||||
result.strokeColor = resources.getColorWithID(R.color.input_bar_button_background_opaque_border, context.theme)
|
||||
result.strokeColor = context.getColorFromAttr(R.attr.input_bar_button_background_opaque_border)
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -72,8 +77,7 @@ class InputBarButton : RelativeLayout {
|
||||
result.layoutParams = LayoutParams(size, size)
|
||||
result.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
result.setImageResource(iconID)
|
||||
val colorID = if (isSendButton) R.color.black else R.color.text
|
||||
result.imageTintList = ColorStateList.valueOf(resources.getColorWithID(colorID, context.theme))
|
||||
result.imageTintList = ColorStateList.valueOf(context.getColorFromAttr(R.attr.input_bar_button_text_color))
|
||||
result
|
||||
}
|
||||
|
||||
@ -104,13 +108,18 @@ class InputBarButton : RelativeLayout {
|
||||
fun getIconID() = iconID
|
||||
|
||||
fun expand() {
|
||||
GlowViewUtilities.animateColorChange(context, imageViewContainer, colorID, R.color.accent)
|
||||
val fromColor = context.getColorFromAttr(colorID)
|
||||
val toColor = context.getAccentColor()
|
||||
GlowViewUtilities.animateColorChange(imageViewContainer, fromColor, toColor)
|
||||
imageViewContainer.animateSizeChange(R.dimen.input_bar_button_collapsed_size, R.dimen.input_bar_button_expanded_size, animationDuration)
|
||||
animateImageViewContainerPositionChange(collapsedImageViewPosition, expandedImageViewPosition)
|
||||
}
|
||||
|
||||
fun collapse() {
|
||||
GlowViewUtilities.animateColorChange(context, imageViewContainer, R.color.accent, colorID)
|
||||
val fromColor = context.getAccentColor()
|
||||
val toColor = context.getColorFromAttr(colorID)
|
||||
|
||||
GlowViewUtilities.animateColorChange(imageViewContainer, fromColor, toColor)
|
||||
imageViewContainer.animateSizeChange(R.dimen.input_bar_button_expanded_size, R.dimen.input_bar_button_collapsed_size, animationDuration)
|
||||
animateImageViewContainerPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import android.widget.Toast
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
@ -27,6 +28,7 @@ import org.session.libsession.messaging.sending_receiving.leave
|
||||
import org.session.libsession.utilities.ExpirationUtil
|
||||
import org.session.libsession.utilities.GroupUtil.doubleDecodeGroupID
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
@ -43,7 +45,6 @@ import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.group
|
||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import java.io.IOException
|
||||
|
||||
object ConversationMenuHelper {
|
||||
@ -69,7 +70,7 @@ object ConversationMenuHelper {
|
||||
val actionView = item.actionView
|
||||
val iconView = actionView.findViewById<ImageView>(R.id.menu_badge_icon)
|
||||
val badgeView = actionView.findViewById<TextView>(R.id.expiration_badge)
|
||||
@ColorInt val color = context.resources.getColorWithID(R.color.text, context.theme)
|
||||
@ColorInt val color = context.getColorFromAttr(android.R.attr.textColorPrimary)
|
||||
iconView.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)
|
||||
badgeView.text = ExpirationUtil.getExpirationAbbreviatedDisplayValue(context, thread.expireMessages)
|
||||
actionView.setOnClickListener { onOptionsItemSelected(item) }
|
||||
@ -328,7 +329,7 @@ object ConversationMenuHelper {
|
||||
}
|
||||
|
||||
private fun mute(context: Context, thread: Recipient) {
|
||||
MuteDialog.show(context) { until: Long ->
|
||||
MuteDialog.show(ContextThemeWrapper(context, context.theme)) { until: Long ->
|
||||
DatabaseComponent.get(context).recipientDatabase().setMuted(thread, until)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.google.android.flexbox.FlexboxLayout;
|
||||
import com.google.android.flexbox.JustifyContent;
|
||||
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||
import org.thoughtcrime.securesms.conversation.v2.ViewUtil;
|
||||
@ -126,14 +127,17 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
|
||||
int innerPadding = ViewUtil.dpToPx(4);
|
||||
overflowContainer.setPaddingRelative(innerPadding,innerPadding,innerPadding,innerPadding);
|
||||
|
||||
int pixelSize = ViewUtil.dpToPx(1);
|
||||
|
||||
for (Reaction reaction : reactions) {
|
||||
if (container.getChildCount() + 1 >= DEFAULT_THRESHOLD && threshold != Integer.MAX_VALUE && reactions.size() > threshold) {
|
||||
if (overflowContainer.getParent() == null) {
|
||||
container.addView(overflowContainer);
|
||||
ViewGroup.LayoutParams overflowParams = overflowContainer.getLayoutParams();
|
||||
MarginLayoutParams overflowParams = (MarginLayoutParams) overflowContainer.getLayoutParams();
|
||||
overflowParams.height = ViewUtil.dpToPx(26);
|
||||
overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize);
|
||||
overflowContainer.setLayoutParams(overflowParams);
|
||||
overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_dialog_background));
|
||||
overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_background));
|
||||
}
|
||||
View pill = buildPill(getContext(), this, reaction, true);
|
||||
pill.setOnClickListener(v -> {
|
||||
@ -147,11 +151,10 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
|
||||
View pill = buildPill(getContext(), this, reaction, false);
|
||||
pill.setTag(reaction);
|
||||
pill.setOnTouchListener(this);
|
||||
container.addView(pill);
|
||||
int pixelSize = ViewUtil.dpToPx(1);
|
||||
MarginLayoutParams params = (MarginLayoutParams) pill.getLayoutParams();
|
||||
params.setMargins(pixelSize, 0, pixelSize, 0);
|
||||
params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize);
|
||||
pill.setLayoutParams(params);
|
||||
container.addView(pill);
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +249,7 @@ public class EmojiReactionsView extends LinearLayout implements View.OnTouchList
|
||||
|
||||
if (reaction.userWasSender && !isCompact) {
|
||||
root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected));
|
||||
countView.setTextColor(ContextCompat.getColor(context, R.color.reactions_pill_selected_text_color));
|
||||
countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor));
|
||||
} else {
|
||||
if (!isCompact) {
|
||||
root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background));
|
||||
|
@ -12,6 +12,7 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.isVisible
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewLinkPreviewBinding
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.thoughtcrime.securesms.components.CornerMask
|
||||
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
|
||||
@ -52,14 +53,13 @@ class LinkPreviewView : LinearLayout {
|
||||
}
|
||||
// Title
|
||||
binding.titleTextView.text = linkPreview.title
|
||||
val textColorID = if (message.isOutgoing && UiModeUtilities.isDayUiMode(context)) {
|
||||
R.color.white
|
||||
val textColorID = if (message.isOutgoing) {
|
||||
R.attr.message_sent_text_color
|
||||
} else {
|
||||
if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.white
|
||||
R.attr.message_received_text_color
|
||||
}
|
||||
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||
binding.titleTextView.setTextColor(context.getColorFromAttr(textColorID))
|
||||
// Body
|
||||
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||
// Corner radii
|
||||
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
||||
cornerMask.setTopLeftRadius(cornerRadii[0])
|
||||
|
@ -1,16 +1,19 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewOpenGroupInvitationBinding
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.JoinOpenGroupDialog
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
|
||||
class OpenGroupInvitationView : LinearLayout {
|
||||
private val binding: ViewOpenGroupInvitationBinding by lazy { ViewOpenGroupInvitationBinding.bind(this) }
|
||||
@ -26,8 +29,11 @@ class OpenGroupInvitationView : LinearLayout {
|
||||
val data = umd.kind as UpdateMessageData.Kind.OpenGroupInvitation
|
||||
this.data = data
|
||||
val iconID = if (message.isOutgoing) R.drawable.ic_globe else R.drawable.ic_plus
|
||||
val backgroundColor = if (!message.isOutgoing) context.getAccentColor()
|
||||
else ContextCompat.getColor(context, R.color.transparent_black_6)
|
||||
with(binding){
|
||||
openGroupInvitationIconImageView.setImageResource(iconID)
|
||||
openGroupInvitationIconBackground.backgroundTintList = ColorStateList.valueOf(backgroundColor)
|
||||
openGroupTitleTextView.text = data.groupName
|
||||
openGroupURLTextView.text = OpenGroupUrlParser.trimQueryParameter(data.groupUrl)
|
||||
openGroupTitleTextView.setTextColor(textColor)
|
||||
|
@ -14,13 +14,14 @@ import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewQuoteBinding
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -89,8 +90,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
||||
binding.quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
|
||||
} else if (attachments != null) {
|
||||
binding.quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
|
||||
val backgroundColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.accent
|
||||
val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme)
|
||||
val backgroundColor = context.getAccentColor()
|
||||
binding.quoteViewAttachmentPreviewContainer.backgroundTintList = ColorStateList.valueOf(backgroundColor)
|
||||
binding.quoteViewAttachmentPreviewImageView.isVisible = false
|
||||
binding.quoteViewAttachmentThumbnailImageView.isVisible = false
|
||||
@ -120,31 +120,19 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
||||
|
||||
// region Convenience
|
||||
@ColorInt private fun getLineColor(isOutgoingMessage: Boolean): Int {
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
return when {
|
||||
mode == Mode.Regular && isLightMode || mode == Mode.Draft && isLightMode -> {
|
||||
ResourcesCompat.getColor(resources, R.color.black, context.theme)
|
||||
}
|
||||
mode == Mode.Regular && !isLightMode -> {
|
||||
if (isOutgoingMessage) {
|
||||
ResourcesCompat.getColor(resources, R.color.black, context.theme)
|
||||
} else {
|
||||
ResourcesCompat.getColor(resources, R.color.accent, context.theme)
|
||||
}
|
||||
}
|
||||
else -> { // Draft & dark mode
|
||||
ResourcesCompat.getColor(resources, R.color.accent, context.theme)
|
||||
}
|
||||
mode == Mode.Regular && !isOutgoingMessage -> context.getColorFromAttr(R.attr.colorAccent)
|
||||
mode == Mode.Regular -> context.getColorFromAttr(R.attr.message_sent_text_color)
|
||||
else -> context.getColorFromAttr(R.attr.colorAccent)
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt private fun getTextColor(isOutgoingMessage: Boolean): Int {
|
||||
if (mode == Mode.Draft) { return ResourcesCompat.getColor(resources, R.color.text, context.theme) }
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
return if (!isOutgoingMessage && !isLightMode) {
|
||||
ResourcesCompat.getColor(resources, R.color.white, context.theme)
|
||||
if (mode == Mode.Draft) { return context.getColorFromAttr(android.R.attr.textColorPrimary) }
|
||||
return if (!isOutgoingMessage) {
|
||||
context.getColorFromAttr(R.attr.message_received_text_color)
|
||||
} else {
|
||||
ResourcesCompat.getColor(resources, R.color.black, context.theme)
|
||||
context.getColorFromAttr(R.attr.message_sent_text_color)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@ -32,7 +31,7 @@ import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
|
||||
@ -44,8 +43,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -70,9 +68,9 @@ class VisibleMessageContentView : LinearLayout {
|
||||
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean,
|
||||
glide: GlideRequests, thread: Recipient, searchQuery: String?, contactIsTrusted: Boolean) {
|
||||
// Background
|
||||
val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
val colorID = if (message.isOutgoing) R.attr.message_sent_background_color else R.attr.message_received_background_color
|
||||
val color = ThemeUtil.getThemedColor(context, colorID)
|
||||
val background = getBackground(message.isOutgoing)
|
||||
val color = if (message.isOutgoing) context.getAccentColor()
|
||||
else context.getColorFromAttr(R.attr.message_received_background_color)
|
||||
val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
|
||||
background.colorFilter = filter
|
||||
binding.contentParent.background = background
|
||||
@ -237,22 +235,8 @@ class VisibleMessageContentView : LinearLayout {
|
||||
private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean =
|
||||
listOf<View>(albumThumbnailView, linkPreviewView, voiceMessageView.root, quoteView.root).none { it.isVisible }
|
||||
|
||||
private fun getBackground(isOutgoing: Boolean, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean): Drawable {
|
||||
val isSingleMessage = (isStartOfMessageCluster && isEndOfMessageCluster)
|
||||
@DrawableRes val backgroundID = when {
|
||||
isSingleMessage -> {
|
||||
if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone
|
||||
}
|
||||
isStartOfMessageCluster -> {
|
||||
if (isOutgoing) R.drawable.message_bubble_background_sent_start else R.drawable.message_bubble_background_received_start
|
||||
}
|
||||
isEndOfMessageCluster -> {
|
||||
if (isOutgoing) R.drawable.message_bubble_background_sent_end else R.drawable.message_bubble_background_received_end
|
||||
}
|
||||
else -> {
|
||||
if (isOutgoing) R.drawable.message_bubble_background_sent_middle else R.drawable.message_bubble_background_received_middle
|
||||
}
|
||||
}
|
||||
private fun getBackground(isOutgoing: Boolean): Drawable {
|
||||
val backgroundID = if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone
|
||||
return ResourcesCompat.getDrawable(resources, backgroundID, context.theme)!!
|
||||
}
|
||||
|
||||
@ -307,13 +291,14 @@ class VisibleMessageContentView : LinearLayout {
|
||||
|
||||
@ColorInt
|
||||
fun getTextColor(context: Context, message: MessageRecord): Int {
|
||||
val isDayUiMode = UiModeUtilities.isDayUiMode(context)
|
||||
val colorID = if (message.isOutgoing) {
|
||||
R.color.black
|
||||
val colorAttribute = if (message.isOutgoing) {
|
||||
// sent
|
||||
R.attr.message_sent_text_color
|
||||
} else {
|
||||
if (isDayUiMode) R.color.black else R.color.white
|
||||
// received
|
||||
R.attr.message_received_text_color
|
||||
}
|
||||
return context.resources.getColorWithID(colorID, context.theme)
|
||||
return context.getColorFromAttr(colorAttribute)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
@ -16,7 +16,6 @@ import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
@ -29,6 +28,7 @@ import org.session.libsession.messaging.contacts.Contact.ContactContext
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
@ -45,7 +45,6 @@ import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.toDp
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import java.util.Date
|
||||
@ -280,7 +279,7 @@ class VisibleMessageView : LinearLayout {
|
||||
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
|
||||
container.layoutParams = containerParams
|
||||
if (message.expiresIn > 0 && !message.isPending) {
|
||||
binding.expirationTimerView.setColorFilter(ResourcesCompat.getColor(resources, R.color.text, context.theme))
|
||||
binding.expirationTimerView.setColorFilter(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
binding.expirationTimerView.isInvisible = false
|
||||
binding.expirationTimerView.setPercentComplete(0.0f)
|
||||
if (message.expireStarted > 0) {
|
||||
@ -311,7 +310,7 @@ class VisibleMessageView : LinearLayout {
|
||||
|
||||
private fun handleIsSelectedChanged() {
|
||||
background = if (snIsSelected) {
|
||||
ColorDrawable(context.resources.getColorWithID(R.color.message_selected, context.theme))
|
||||
ColorDrawable(context.getColorFromAttr(R.attr.message_selected))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
||||
@ -64,6 +66,8 @@ open class KThumbnailView: FrameLayout {
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
val background = ContextCompat.getColor(context, R.color.transparent_black_6)
|
||||
binding.root.background = ColorDrawable(background)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
|
@ -15,6 +15,7 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object MentionUtilities {
|
||||
@ -58,13 +59,12 @@ object MentionUtilities {
|
||||
}
|
||||
val result = SpannableString(text)
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
val color = if (isOutgoingMessage) {
|
||||
ResourcesCompat.getColor(context.resources, if (isLightMode) R.color.white else R.color.black, context.theme)
|
||||
} else {
|
||||
context.getAccentColor()
|
||||
}
|
||||
for (mention in mentions) {
|
||||
val colorID = if (isOutgoingMessage) {
|
||||
if (isLightMode) R.color.white else R.color.black
|
||||
} else {
|
||||
R.color.accent
|
||||
}
|
||||
val color = ResourcesCompat.getColor(context.resources, colorID, context.theme)
|
||||
result.setSpan(ForegroundColorSpan(color), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ import android.graphics.Rect
|
||||
import android.os.SystemClock
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import kotlin.math.sin
|
||||
|
||||
class ThumbnailProgressBar: View {
|
||||
@ -25,7 +24,7 @@ class ThumbnailProgressBar: View {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.FILL
|
||||
color = ResourcesCompat.getColor(resources, R.color.accent, null)
|
||||
color = context.getAccentColor()
|
||||
}
|
||||
|
||||
private val objectRect = Rect()
|
||||
|
@ -72,6 +72,11 @@ public abstract class Database {
|
||||
context.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null);
|
||||
}
|
||||
|
||||
protected void notifyRecipientListeners() {
|
||||
context.getContentResolver().notifyChange(DatabaseContentProviders.Recipient.CONTENT_URI, null);
|
||||
notifyConversationListListeners();
|
||||
}
|
||||
|
||||
protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
|
||||
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
|
||||
}
|
||||
|
@ -38,6 +38,10 @@ public class DatabaseContentProviders {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.stickerpack");
|
||||
}
|
||||
|
||||
public static class Recipient extends NoopContentProvider {
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.recipient");
|
||||
}
|
||||
|
||||
private static abstract class NoopContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RecipientDatabase extends Database {
|
||||
@ -232,6 +233,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(COLOR, color.serialize());
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setColor(color);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) {
|
||||
@ -239,6 +241,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId));
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) {
|
||||
@ -246,6 +249,7 @@ public class RecipientDatabase extends Database {
|
||||
contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.resolve().setForceSmsSelection(forceSmsSelection);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setApproved(@NonNull Recipient recipient, boolean approved) {
|
||||
@ -253,6 +257,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(APPROVED, approved ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setApproved(approved);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
|
||||
@ -260,6 +265,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(APPROVED_ME, approvedMe ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setHasApprovedMe(approvedMe);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
|
||||
@ -267,6 +273,24 @@ public class RecipientDatabase extends Database {
|
||||
values.put(BLOCK, blocked ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setBlocked(blocked);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setBlocked(@NonNull List<Recipient> recipients, boolean blocked) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BLOCK, blocked ? 1 : 0);
|
||||
for (Recipient recipient : recipients) {
|
||||
db.update(TABLE_NAME, values, ADDRESS + " = ?", new String[]{recipient.getAddress().serialize()});
|
||||
recipient.resolve().setBlocked(blocked);
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setMuted(@NonNull Recipient recipient, long until) {
|
||||
@ -274,6 +298,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(MUTE_UNTIL, until);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setMuted(until);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,6 +312,7 @@ public class RecipientDatabase extends Database {
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setNotifyType(notifyType);
|
||||
notifyConversationListListeners();
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setExpireMessages(@NonNull Recipient recipient, int expiration) {
|
||||
@ -296,6 +322,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(EXPIRE_MESSAGES, expiration);
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setExpireMessages(expiration);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
|
||||
@ -303,6 +330,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) {
|
||||
@ -310,6 +338,7 @@ public class RecipientDatabase extends Database {
|
||||
values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
|
||||
updateOrInsert(recipient.getAddress(), values);
|
||||
recipient.resolve().setProfileKey(profileKey);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
|
||||
@ -317,6 +346,7 @@ public class RecipientDatabase extends Database {
|
||||
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.resolve().setProfileAvatar(profileAvatar);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
|
||||
@ -325,6 +355,7 @@ public class RecipientDatabase extends Database {
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.resolve().setName(profileName);
|
||||
recipient.resolve().setProfileName(profileName);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
|
||||
@ -332,6 +363,7 @@ public class RecipientDatabase extends Database {
|
||||
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.setProfileSharing(enabled);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) {
|
||||
@ -339,6 +371,7 @@ public class RecipientDatabase extends Database {
|
||||
contentValues.put(NOTIFICATION_CHANNEL, notificationChannel);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.setNotificationChannel(notificationChannel);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) {
|
||||
@ -346,6 +379,7 @@ public class RecipientDatabase extends Database {
|
||||
contentValues.put(REGISTERED, registeredState.getId());
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.setRegistered(registeredState);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
private void updateOrInsert(Address address, ContentValues contentValues) {
|
||||
@ -365,6 +399,22 @@ public class RecipientDatabase extends Database {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
public List<Recipient> getBlockedContacts() {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
|
||||
Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1",
|
||||
null, null, null, null, null);
|
||||
|
||||
RecipientReader reader = new RecipientReader(context, cursor);
|
||||
List<Recipient> returnList = new ArrayList<>();
|
||||
Recipient current;
|
||||
while ((current = reader.getNext()) != null) {
|
||||
returnList.add(current);
|
||||
}
|
||||
reader.close();
|
||||
return returnList;
|
||||
}
|
||||
|
||||
public static class RecipientReader implements Closeable {
|
||||
|
||||
private final Context context;
|
||||
|
@ -952,4 +952,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
|
||||
}
|
||||
|
||||
override fun unblock(toUnblock: List<Recipient>) {
|
||||
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
|
||||
recipientDb.setBlocked(toUnblock, false)
|
||||
}
|
||||
|
||||
override fun blockedContacts(): List<Recipient> {
|
||||
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
|
||||
return recipientDb.blockedContacts
|
||||
}
|
||||
|
||||
}
|
@ -50,6 +50,7 @@ public class ThreadRecord extends DisplayRecord {
|
||||
private final long expiresIn;
|
||||
private final long lastSeen;
|
||||
private final boolean pinned;
|
||||
private final int recipientHash;
|
||||
|
||||
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
|
||||
@NonNull Recipient recipient, long date, long count, int unreadCount,
|
||||
@ -65,13 +66,18 @@ public class ThreadRecord extends DisplayRecord {
|
||||
this.archived = archived;
|
||||
this.expiresIn = expiresIn;
|
||||
this.lastSeen = lastSeen;
|
||||
this.pinned = pinned;
|
||||
this.pinned = pinned;
|
||||
this.recipientHash = recipient.hashCode();
|
||||
}
|
||||
|
||||
public @Nullable Uri getSnippetUri() {
|
||||
return snippetUri;
|
||||
}
|
||||
|
||||
public int getRecipientHash() {
|
||||
return recipientHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||
if (isGroupUpdateMessage()) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
@ -9,7 +8,7 @@ import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||
|
||||
class PlaceholderAvatarLoader(private val context: Context): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||
class PlaceholderAvatarLoader(): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||
|
||||
override fun buildLoadData(
|
||||
model: PlaceholderAvatarPhoto,
|
||||
@ -17,14 +16,14 @@ class PlaceholderAvatarLoader(private val context: Context): ModelLoader<Placeho
|
||||
height: Int,
|
||||
options: Options
|
||||
): LoadData<BitmapDrawable> {
|
||||
return LoadData(model, PlaceholderAvatarFetcher(context, model))
|
||||
return LoadData(model, PlaceholderAvatarFetcher(model.context, model))
|
||||
}
|
||||
|
||||
override fun handles(model: PlaceholderAvatarPhoto): Boolean = true
|
||||
|
||||
class Factory(private val context: Context) : ModelLoaderFactory<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||
class Factory() : ModelLoaderFactory<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<PlaceholderAvatarPhoto, BitmapDrawable> {
|
||||
return PlaceholderAvatarLoader(context)
|
||||
return PlaceholderAvatarLoader()
|
||||
}
|
||||
override fun teardown() {}
|
||||
}
|
||||
|
@ -7,9 +7,12 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.FragmentCreateGroupBinding
|
||||
@ -57,6 +60,12 @@ class CreateGroupFragment : Fragment() {
|
||||
}
|
||||
binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
|
||||
binding.recyclerView.adapter = adapter
|
||||
val divider = ContextCompat.getDrawable(requireActivity(), R.drawable.conversation_menu_divider)!!.let {
|
||||
DividerItemDecoration(requireActivity(), RecyclerView.VERTICAL).apply {
|
||||
setDrawable(it)
|
||||
}
|
||||
}
|
||||
binding.recyclerView.addItemDecoration(divider)
|
||||
var isLoading = false
|
||||
binding.createClosedGroupButton.setOnClickListener {
|
||||
if (isLoading) return@setOnClickListener
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -10,7 +11,7 @@ import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
|
||||
class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListener {
|
||||
class ConversationOptionsBottomSheet(private val parentContext: Context) : BottomSheetDialogFragment(), View.OnClickListener {
|
||||
private lateinit var binding: FragmentConversationBottomSheetBinding
|
||||
//FIXME AC: Supplying a threadRecord directly into the field from an activity
|
||||
// is not the best idea. It doesn't survive configuration change.
|
||||
@ -28,8 +29,8 @@ class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClick
|
||||
var onNotificationTapped: (() -> Unit)? = null
|
||||
var onSetMuteTapped: ((Boolean) -> Unit)? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentConversationBottomSheetBinding.inflate(inflater, container, false)
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentConversationBottomSheetBinding.inflate(LayoutInflater.from(parentContext), container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.home
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
@ -20,6 +21,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import java.util.Locale
|
||||
|
||||
class ConversationView : LinearLayout {
|
||||
@ -41,11 +43,19 @@ class ConversationView : LinearLayout {
|
||||
// region Updating
|
||||
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
|
||||
this.thread = thread
|
||||
background = if (thread.isPinned) {
|
||||
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_pin, 0)
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_pinned_background)
|
||||
if (thread.isPinned) {
|
||||
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
0,
|
||||
0,
|
||||
R.drawable.ic_pin,
|
||||
0
|
||||
)
|
||||
} else {
|
||||
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
background = if (thread.unreadCount > 0) {
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_unread_background)
|
||||
} else {
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_view_background)
|
||||
}
|
||||
binding.profilePictureView.root.glide = glide
|
||||
@ -54,7 +64,9 @@ class ConversationView : LinearLayout {
|
||||
binding.accentView.setBackgroundResource(R.color.destructive)
|
||||
binding.accentView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.accentView.setBackgroundResource(R.color.accent)
|
||||
val accentColor = context.getAccentColor()
|
||||
val background = ColorDrawable(accentColor)
|
||||
binding.accentView.background = background
|
||||
// Using thread.isRead we can determine if the last message was our own, and display it as 'read' even though previous messages may not be
|
||||
// This would also not trigger the disappearing message timer which may or may not be desirable
|
||||
binding.accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE
|
||||
@ -65,9 +77,9 @@ class ConversationView : LinearLayout {
|
||||
if (unreadCount < 10000) unreadCount.toString() else "9999+"
|
||||
}
|
||||
binding.unreadCountTextView.text = formattedUnreadCount
|
||||
val textSize = if (unreadCount < 10000) 12.0f else 9.0f
|
||||
val textSize = if (unreadCount < 1000) 12.0f else 10.0f
|
||||
binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||
binding.unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
|
||||
binding.unreadCountIndicator.background.setTint(context.getAccentColor())
|
||||
binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||
?: thread.recipient.address.toString()
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -9,6 +8,7 @@ import android.os.Bundle
|
||||
import android.text.SpannableString
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.preferences.SettingsActivity
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.IP2Country
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
@ -167,7 +166,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
binding.seedReminderView.isVisible = false
|
||||
}
|
||||
setupMessageRequestsBanner()
|
||||
setupHeaderImage()
|
||||
// Set up recycler view
|
||||
binding.globalSearchInputLayout.listener = this
|
||||
homeAdapter.setHasStableIds(true)
|
||||
@ -276,12 +274,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
EventBus.getDefault().register(this@HomeActivity)
|
||||
}
|
||||
|
||||
private fun setupHeaderImage() {
|
||||
val isDayUiMode = UiModeUtilities.isDayUiMode(this)
|
||||
val headerTint = if (isDayUiMode) R.color.black else R.color.white
|
||||
binding.sessionHeaderImage.setColorFilter(getColor(headerTint))
|
||||
}
|
||||
|
||||
override fun onInputFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) {
|
||||
setSearchShown(true)
|
||||
@ -296,7 +288,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
binding.recyclerView.isVisible = !isShown
|
||||
binding.emptyStateContainer.isVisible = (binding.recyclerView.adapter as NewHomeAdapter).itemCount == 0 && binding.recyclerView.isVisible
|
||||
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
|
||||
binding.gradientView.isVisible = !isShown
|
||||
binding.globalSearchRecycler.isVisible = isShown
|
||||
binding.newConversationButton.isVisible = !isShown
|
||||
}
|
||||
@ -405,7 +396,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
|
||||
override fun onLongConversationClick(thread: ThreadRecord) {
|
||||
val bottomSheet = ConversationOptionsBottomSheet()
|
||||
val bottomSheet = ConversationOptionsBottomSheet(this)
|
||||
bottomSheet.thread = thread
|
||||
bottomSheet.onViewDetailsTapped = {
|
||||
bottomSheet.dismiss()
|
||||
|
@ -28,10 +28,8 @@ class HomeDiffUtil(
|
||||
if (!sameUnreads) return false
|
||||
val samePinned = oldItem.isPinned == newItem.isPinned
|
||||
if (!samePinned) return false
|
||||
val sameAvatar = oldItem.recipient.profileAvatar == newItem.recipient.profileAvatar
|
||||
if (!sameAvatar) return false
|
||||
val sameUsername = oldItem.recipient.name == newItem.recipient.name
|
||||
if (!sameUsername) return false
|
||||
val sameRecipientHash = oldItem.recipientHash == newItem.recipientHash
|
||||
if (!sameRecipientHash) return false
|
||||
val sameSnippet = oldItem.getDisplayBody(context) == newItem.getDisplayBody(context)
|
||||
if (!sameSnippet) return false
|
||||
val sameSendStatus = oldItem.isFailed == newItem.isFailed && oldItem.isDelivered == newItem.isDelivered
|
||||
|
@ -20,6 +20,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityPathBinding
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
@ -30,6 +31,7 @@ import org.thoughtcrime.securesms.util.animateSizeChange
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.fadeIn
|
||||
import org.thoughtcrime.securesms.util.fadeOut
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
|
||||
class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
@ -131,7 +133,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
lineView.layoutParams = lineViewLayoutParams
|
||||
mainContainer.addView(lineView)
|
||||
val titleTextView = TextView(this)
|
||||
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||
titleTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
|
||||
titleTextView.text = title
|
||||
titleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
|
||||
@ -144,7 +146,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
mainContainer.addView(titleContainer)
|
||||
if (subtitle != null) {
|
||||
val subtitleTextView = TextView(this)
|
||||
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||
subtitleTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
||||
subtitleTextView.text = subtitle
|
||||
subtitleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
|
||||
@ -185,7 +187,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private val dotView by lazy {
|
||||
val result = PathDotView(context)
|
||||
result.setBackgroundResource(R.drawable.accent_dot)
|
||||
result.mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
result.mainColor = context.getAccentColor()
|
||||
result
|
||||
}
|
||||
|
||||
@ -219,7 +221,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private fun setUpViewHierarchy() {
|
||||
disableClipping()
|
||||
val lineView = View(context)
|
||||
lineView.setBackgroundColor(resources.getColorWithID(R.color.text, context.theme))
|
||||
lineView.setBackgroundColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
val lineViewHeight = when (location) {
|
||||
Location.Top, Location.Bottom -> resources.getDimensionPixelSize(R.dimen.path_row_height) / 2
|
||||
Location.Middle -> resources.getDimensionPixelSize(R.dimen.path_row_height)
|
||||
@ -255,13 +257,17 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private fun expand() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
|
||||
@ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, dotView, startColorID, R.color.accent)
|
||||
val startColor = context.resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = context.getAccentColor()
|
||||
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
|
||||
}
|
||||
|
||||
private fun collapse() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
|
||||
@ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, dotView, R.color.accent, endColorID)
|
||||
val startColor = context.getAccentColor()
|
||||
val endColor = context.resources.getColorWithID(endColorID, context.theme)
|
||||
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
@ -6,10 +6,10 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
@ -46,7 +46,9 @@ class PathStatusView : View {
|
||||
}
|
||||
|
||||
private fun initialize() {
|
||||
update()
|
||||
if (!isInEditMode) {
|
||||
update()
|
||||
}
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
|
||||
@ -87,12 +89,14 @@ class PathStatusView : View {
|
||||
private fun update() {
|
||||
if (OnionRequestAPI.paths.isNotEmpty()) {
|
||||
setBackgroundResource(R.drawable.accent_dot)
|
||||
mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
val hasPathsColor = context.getColor(R.color.accent_green)
|
||||
mainColor = hasPathsColor
|
||||
sessionShadowColor = hasPathsColor
|
||||
} else {
|
||||
setBackgroundResource(R.drawable.paths_building_dot)
|
||||
mainColor = resources.getColorWithID(R.color.paths_building, context.theme)
|
||||
sessionShadowColor = resources.getColorWithID(R.color.paths_building, context.theme)
|
||||
val pathsBuildingColor = resources.getColorWithID(R.color.paths_building, context.theme)
|
||||
mainColor = pathsBuildingColor
|
||||
sessionShadowColor = pathsBuildingColor
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -30,7 +31,7 @@ import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UserDetailsBottomSheet : BottomSheetDialogFragment() {
|
||||
class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
||||
|
||||
@Inject lateinit var threadDb: ThreadDatabase
|
||||
|
||||
@ -41,7 +42,9 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentUserDetailsBottomSheetBinding.inflate(inflater, container, false)
|
||||
val wrappedContext = ContextThemeWrapper(requireActivity(), requireActivity().theme)
|
||||
val themedInflater = inflater.cloneInContext(wrappedContext)
|
||||
binding = FragmentUserDetailsBottomSheetBinding.inflate(themedInflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,6 @@ class KeyboardPageSearchView @JvmOverloads constructor(
|
||||
val iconTint = typedArray.getColorStateList(R.styleable.KeyboardPageSearchView_search_icon_tint) ?: ContextCompat.getColorStateList(context, R.color.signal_icon_tint_tab_selected)
|
||||
ImageViewCompat.setImageTintList(navButton, iconTint)
|
||||
ImageViewCompat.setImageTintList(clearButton, iconTint)
|
||||
input.setHintTextColor(iconTint)
|
||||
|
||||
val clickOnly: Boolean = typedArray.getBoolean(R.styleable.KeyboardPageSearchView_click_only, false)
|
||||
if (clickOnly) {
|
||||
|
@ -5,11 +5,11 @@ import android.content.res.ColorStateList
|
||||
import android.database.Cursor
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupMenu
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
@ -47,7 +47,7 @@ class MessageRequestsAdapter(
|
||||
}
|
||||
|
||||
private fun showPopupMenu(view: MessageRequestView) {
|
||||
val popupMenu = PopupMenu(context, view)
|
||||
val popupMenu = PopupMenu(ContextThemeWrapper(context, R.style.PopupMenu_MessageRequests), view)
|
||||
popupMenu.menuInflater.inflate(R.menu.menu_message_request, popupMenu.menu)
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
if (menuItem.itemId == R.id.menu_delete_message_request) {
|
||||
|
@ -73,7 +73,7 @@ public class SignalGlideModule extends AppGlideModule {
|
||||
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
|
||||
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
||||
registry.append(ChunkedImageUrl.class, InputStream.class, new ChunkedImageUrlLoader.Factory());
|
||||
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory(context));
|
||||
registry.append(PlaceholderAvatarPhoto.class, BitmapDrawable.class, new PlaceholderAvatarLoader.Factory());
|
||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.notifications;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@ -65,17 +64,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
|
||||
}
|
||||
|
||||
private void setLed() {
|
||||
String ledColor = TextSecurePreferences.getNotificationLedColor(context);
|
||||
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
|
||||
String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
|
||||
|
||||
if (!ledColor.equals("none")) {
|
||||
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
|
||||
|
||||
setLights(Color.parseColor(ledColor),
|
||||
Integer.parseInt(blinkPatternArray[0]),
|
||||
Integer.parseInt(blinkPatternArray[1]));
|
||||
}
|
||||
int ledColor = TextSecurePreferences.getNotificationLedColor(context);
|
||||
setLights(ledColor, 500,2000);
|
||||
}
|
||||
|
||||
public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) {
|
||||
@ -88,13 +78,6 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
|
||||
}
|
||||
}
|
||||
|
||||
private String[] parseBlinkPattern(String blinkPattern, String blinkPatternCustom) {
|
||||
if (blinkPattern.equals("custom"))
|
||||
blinkPattern = blinkPatternCustom;
|
||||
|
||||
return blinkPattern.split(",");
|
||||
}
|
||||
|
||||
protected @NonNull CharSequence trimToDisplayLength(@Nullable CharSequence text) {
|
||||
text = text == null ? "" : text;
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
package org.thoughtcrime.securesms.notifications;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioAttributes;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
@ -221,7 +219,7 @@ public class NotificationChannels {
|
||||
* channels. Performs database operations and should therefore be invoked on a background thread.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull String color) {
|
||||
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull Integer color) {
|
||||
if (!supported()) {
|
||||
return;
|
||||
}
|
||||
@ -472,12 +470,12 @@ public class NotificationChannels {
|
||||
}
|
||||
|
||||
@TargetApi(26)
|
||||
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull String ledColor) {
|
||||
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull Integer ledColor) {
|
||||
if ("none".equals(ledColor)) {
|
||||
channel.enableLights(false);
|
||||
} else {
|
||||
channel.enableLights(true);
|
||||
channel.setLightColor(Color.parseColor(ledColor));
|
||||
channel.setLightColor(ledColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,7 +507,7 @@ public class NotificationChannels {
|
||||
|
||||
@WorkerThread
|
||||
@TargetApi(26)
|
||||
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull String color) {
|
||||
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull Integer color) {
|
||||
RecipientDatabase database = DatabaseComponent.get(context).recipientDatabase();
|
||||
|
||||
try (RecipientDatabase.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) {
|
||||
|
@ -22,6 +22,7 @@ import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Action;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
@ -63,9 +64,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
||||
{
|
||||
super(context, privacy);
|
||||
|
||||
|
||||
setSmallIcon(R.drawable.ic_notification);
|
||||
setColor(context.getResources().getColor(R.color.textsecure_primary));
|
||||
setColor(ContextCompat.getColor(context, R.color.accent_green));
|
||||
setCategory(NotificationCompat.CATEGORY_MESSAGE);
|
||||
|
||||
if (!NotificationChannels.supported()) {
|
||||
|
@ -11,20 +11,22 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityPnModeBinding
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.home.HomeActivity
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.PNModeView
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.util.PNModeView
|
||||
|
||||
class PNModeActivity : BaseActionBarActivity() {
|
||||
private lateinit var binding: ActivityPnModeBinding
|
||||
@ -40,10 +42,10 @@ class PNModeActivity : BaseActionBarActivity() {
|
||||
with(binding) {
|
||||
contentView.disableClipping()
|
||||
fcmOptionView.setOnClickListener { toggleFCM() }
|
||||
fcmOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
|
||||
fcmOptionView.mainColor = ThemeUtil.getThemedColor(root.context, R.attr.colorPrimary)
|
||||
fcmOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
|
||||
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
|
||||
backgroundPollingOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
|
||||
backgroundPollingOptionView.mainColor = ThemeUtil.getThemedColor(root.context, R.attr.colorPrimary)
|
||||
backgroundPollingOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
|
||||
registerButton.setOnClickListener { register() }
|
||||
}
|
||||
@ -84,60 +86,60 @@ class PNModeActivity : BaseActionBarActivity() {
|
||||
}
|
||||
|
||||
private fun toggleFCM() = with(binding) {
|
||||
val accentColor = getAccentColor()
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
|
||||
GlowViewUtilities.animateShadowColorChange(fcmOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
|
||||
animateStrokeColorChange(fcmOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
|
||||
GlowViewUtilities.animateShadowColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
|
||||
animateStrokeColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
|
||||
selectedOptionView = null
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
|
||||
GlowViewUtilities.animateShadowColorChange(fcmOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
|
||||
animateStrokeColorChange(fcmOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
|
||||
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
|
||||
animateStrokeColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBackgroundPolling() = with(binding) {
|
||||
val accentColor = getAccentColor()
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
|
||||
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
|
||||
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
|
||||
animateStrokeColorChange(backgroundPollingOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
|
||||
selectedOptionView = null
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, backgroundPollingOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
|
||||
GlowViewUtilities.animateShadowColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.transparent, theme), accentColor)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, resources.getColorWithID(R.color.pn_option_border, theme), accentColor)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this@PNModeActivity, fcmOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
|
||||
GlowViewUtilities.animateShadowColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.transparent, theme))
|
||||
animateStrokeColorChange(fcmOptionView, accentColor, resources.getColorWithID(R.color.pn_option_border, theme))
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateStrokeColorChange(bubble: PNModeView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = resources.getColorWithID(startColorID, theme)
|
||||
val endColor = resources.getColorWithID(endColorID, theme)
|
||||
private fun animateStrokeColorChange(bubble: PNModeView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
|
@ -12,12 +12,13 @@ import android.widget.Toast
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivitySeedBinding
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsignal.crypto.MnemonicCodec
|
||||
import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
|
||||
class SeedActivity : BaseActionBarActivity() {
|
||||
|
||||
@ -41,7 +42,7 @@ class SeedActivity : BaseActionBarActivity() {
|
||||
setContentView(binding.root)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_seed_title)
|
||||
val seedReminderViewTitle = SpannableString("You're almost finished! 90%") // Intentionally not yet translated
|
||||
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
seedReminderViewTitle.setSpan(ForegroundColorSpan(getAccentColor()), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
with(binding) {
|
||||
seedReminderView.title = seedReminderViewTitle
|
||||
seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_2)
|
||||
@ -55,7 +56,7 @@ class SeedActivity : BaseActionBarActivity() {
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
seedTextView.setTextColor(resources.getColorWithID(R.color.accent, theme))
|
||||
seedTextView.setTextColor(getAccentColor())
|
||||
seedTextView.text = redactedSeed
|
||||
seedTextView.setOnLongClickListener { revealSeed(); true }
|
||||
revealButton.setOnLongClickListener { revealSeed(); true }
|
||||
@ -67,7 +68,7 @@ class SeedActivity : BaseActionBarActivity() {
|
||||
// region Updating
|
||||
private fun revealSeed() {
|
||||
val seedReminderViewTitle = SpannableString("Account secured! 100%") // Intentionally not yet translated
|
||||
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 17, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
seedReminderViewTitle.setSpan(ForegroundColorSpan(getAccentColor()), 17, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
with(binding) {
|
||||
seedReminderView.title = seedReminderViewTitle
|
||||
seedReminderView.subtitle = resources.getString(R.string.view_seed_reminder_subtitle_3)
|
||||
@ -75,7 +76,7 @@ class SeedActivity : BaseActionBarActivity() {
|
||||
val seedTextViewLayoutParams = seedTextView.layoutParams as LinearLayout.LayoutParams
|
||||
seedTextViewLayoutParams.height = seedTextView.height
|
||||
seedTextView.layoutParams = seedTextViewLayoutParams
|
||||
seedTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||
seedTextView.setTextColor(getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
seedTextView.text = seed
|
||||
}
|
||||
TextSecurePreferences.setHasViewedSeed(this, true)
|
||||
|
@ -22,8 +22,8 @@ import androidx.fragment.app.Fragment;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.annimon.stream.function.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.session.libsession.utilities.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.SecureRandom;
|
||||
@ -353,7 +353,7 @@ public class Permissions {
|
||||
Context context = this.context.get();
|
||||
|
||||
if (context != null) {
|
||||
new AlertDialog.Builder(context)
|
||||
new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog)
|
||||
.setTitle(R.string.Permissions_permission_required)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.Permissions_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context)))
|
||||
|
@ -4,8 +4,6 @@ package org.thoughtcrime.securesms.permissions;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -14,13 +12,18 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class RationaleDialog {
|
||||
|
||||
public static AlertDialog.Builder createFor(@NonNull Context context, @NonNull String message, @DrawableRes int... drawables) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.permissions_rationale_dialog, null);
|
||||
view.setClipToOutline(true);
|
||||
ViewGroup header = view.findViewById(R.id.header_container);
|
||||
TextView text = view.findViewById(R.id.message);
|
||||
|
||||
@ -47,7 +50,7 @@ public class RationaleDialog {
|
||||
|
||||
text.setText(message);
|
||||
|
||||
return new AlertDialog.Builder(context, R.style.Theme_TextSecure_Dialog_Rationale).setView(view);
|
||||
return new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog).setView(view);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityBlockedContactsBinding
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener {
|
||||
|
||||
lateinit var binding: ActivityBlockedContactsBinding
|
||||
|
||||
val viewModel: BlockedContactsViewModel by viewModels()
|
||||
|
||||
val adapter = BlockedContactsAdapter()
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (v === binding.unblockButton && adapter.getSelectedItems().isNotEmpty()) {
|
||||
val contactsToUnblock = adapter.getSelectedItems()
|
||||
// show dialog
|
||||
val title = if (contactsToUnblock.size == 1) {
|
||||
getString(R.string.Unblock_dialog__title_single, contactsToUnblock.first().name)
|
||||
} else {
|
||||
getString(R.string.Unblock_dialog__title_multiple)
|
||||
}
|
||||
|
||||
val message = if (contactsToUnblock.size == 1) {
|
||||
getString(R.string.Unblock_dialog__message, contactsToUnblock.first().name)
|
||||
} else {
|
||||
val stringBuilder = StringBuilder()
|
||||
val iterator = contactsToUnblock.iterator()
|
||||
var numberAdded = 0
|
||||
while (iterator.hasNext() && numberAdded < 3) {
|
||||
val nextRecipient = iterator.next()
|
||||
if (numberAdded > 0) stringBuilder.append(", ")
|
||||
|
||||
stringBuilder.append(nextRecipient.name)
|
||||
numberAdded++
|
||||
}
|
||||
val overflow = contactsToUnblock.size - numberAdded
|
||||
if (overflow > 0) {
|
||||
stringBuilder.append(" ")
|
||||
val string = resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow)
|
||||
stringBuilder.append(string.format(overflow))
|
||||
}
|
||||
getString(R.string.Unblock_dialog__message, stringBuilder.toString())
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.continue_2) { d, _ ->
|
||||
viewModel.unblock(contactsToUnblock)
|
||||
d.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { d, _ ->
|
||||
d.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
binding = ActivityBlockedContactsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
viewModel.subscribe(this)
|
||||
.observe(this) { newState ->
|
||||
adapter.submitList(newState.blockedContacts)
|
||||
val isEmpty = newState.blockedContacts.isEmpty()
|
||||
binding.emptyStateMessageTextView.isVisible = isEmpty
|
||||
binding.nonEmptyStateGroup.isVisible = !isEmpty
|
||||
}
|
||||
|
||||
binding.unblockButton.setOnClickListener(this)
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.BlockedContactLayoutBinding
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
||||
class BlockedContactsAdapter: ListAdapter<Recipient,BlockedContactsAdapter.ViewHolder>(RecipientDiffer()) {
|
||||
|
||||
class RecipientDiffer: DiffUtil.ItemCallback<Recipient>() {
|
||||
override fun areItemsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem === newItem
|
||||
override fun areContentsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem == newItem
|
||||
}
|
||||
|
||||
private val selectedItems = mutableListOf<Recipient>()
|
||||
|
||||
fun getSelectedItems() = selectedItems
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.blocked_contact_layout, parent, false)
|
||||
return ViewHolder(itemView)
|
||||
}
|
||||
|
||||
private fun toggleSelection(recipient: Recipient, isSelected: Boolean, position: Int) {
|
||||
if (isSelected) {
|
||||
selectedItems -= recipient
|
||||
} else {
|
||||
selectedItems += recipient
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val recipient = getItem(position)
|
||||
val isSelected = recipient in selectedItems
|
||||
holder.bind(recipient, isSelected) {
|
||||
toggleSelection(recipient, isSelected, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
holder.binding.profilePictureView.root.recycle()
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
val glide = GlideApp.with(itemView)
|
||||
val binding = BlockedContactLayoutBinding.bind(itemView)
|
||||
|
||||
fun bind(recipient: Recipient, isSelected: Boolean, toggleSelection: () -> Unit) {
|
||||
binding.recipientName.text = recipient.name
|
||||
with (binding.profilePictureView.root) {
|
||||
glide = this@ViewHolder.glide
|
||||
update(recipient)
|
||||
}
|
||||
binding.root.setOnClickListener { toggleSelection() }
|
||||
binding.selectButton.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
|
||||
class BlockedContactsLayout @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs)
|
@ -0,0 +1,28 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
|
||||
class BlockedContactsPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attributeSet: AttributeSet? = null) : PreferenceCategory(context, attributeSet), View.OnClickListener {
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (v is BlockedContactsLayout) {
|
||||
val intent = Intent(context, BlockedContactsActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
|
||||
val itemView = holder.itemView
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.cash.copper.flow.observeQuery
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BlockedContactsViewModel @Inject constructor(private val storage: Storage): ViewModel() {
|
||||
|
||||
private val executor = viewModelScope + SupervisorJob()
|
||||
|
||||
private val listUpdateChannel = Channel<Unit>(capacity = Channel.CONFLATED)
|
||||
|
||||
private val _contacts = MutableLiveData(BlockedContactsViewState(emptyList()))
|
||||
|
||||
fun subscribe(context: Context): LiveData<BlockedContactsViewState> {
|
||||
executor.launch(IO) {
|
||||
context.contentResolver
|
||||
.observeQuery(DatabaseContentProviders.Recipient.CONTENT_URI)
|
||||
.onStart {
|
||||
listUpdateChannel.trySend(Unit)
|
||||
}
|
||||
.onEach {
|
||||
listUpdateChannel.trySend(Unit)
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
executor.launch(IO) {
|
||||
for (update in listUpdateChannel) {
|
||||
val blockedContactState = BlockedContactsViewState(storage.blockedContacts().sortedBy { it.name })
|
||||
withContext(Main) {
|
||||
_contacts.value = blockedContactState
|
||||
}
|
||||
}
|
||||
}
|
||||
return _contacts
|
||||
}
|
||||
|
||||
fun unblock(toUnblock: List<Recipient>) {
|
||||
storage.unblock(toUnblock)
|
||||
}
|
||||
|
||||
data class BlockedContactsViewState(
|
||||
val blockedContacts: List<Recipient>
|
||||
)
|
||||
|
||||
}
|
@ -2,11 +2,7 @@ package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.UiMode
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
|
||||
class ChangeUiModeDialog : DialogFragment() {
|
||||
|
||||
@ -16,19 +12,8 @@ class ChangeUiModeDialog : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val context = requireContext()
|
||||
|
||||
val displayNameList = UiMode.values().map { getString(it.displayNameRes) }.toTypedArray()
|
||||
val activeUiMode = UiModeUtilities.getUserSelectedUiMode(context)
|
||||
|
||||
return AlertDialog.Builder(context)
|
||||
.setSingleChoiceItems(displayNameList, activeUiMode.ordinal) { _, selectedItemIdx: Int ->
|
||||
val uiMode = UiMode.values()[selectedItemIdx]
|
||||
UiModeUtilities.setUserSelectedUiMode(context, uiMode)
|
||||
dismiss()
|
||||
requireActivity().recreate()
|
||||
}
|
||||
.setTitle(R.string.dialog_ui_mode_title)
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
|
||||
.create()
|
||||
return android.app.AlertDialog.Builder(context)
|
||||
.setTitle("TODO: remove this")
|
||||
.show()
|
||||
}
|
||||
}
|
@ -3,14 +3,13 @@ package org.thoughtcrime.securesms.preferences
|
||||
import android.os.Bundle
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment
|
||||
|
||||
class ChatSettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
setContentView(R.layout.activity_fragment_wrapper)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_chat_settings_title)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_conversations_settings_title)
|
||||
val fragment = ChatsPreferenceFragment()
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
transaction.replace(R.id.fragmentContainer, fragment)
|
||||
|
@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@ -13,8 +15,8 @@ import network.loki.messenger.databinding.DialogClearAllDataBinding
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
|
||||
class ClearAllDataDialog : BaseDialog() {
|
||||
private lateinit var binding: DialogClearAllDataBinding
|
||||
@ -26,9 +28,6 @@ class ClearAllDataDialog : BaseDialog() {
|
||||
}
|
||||
|
||||
var clearJob: Job? = null
|
||||
set(value) {
|
||||
field = value
|
||||
}
|
||||
|
||||
var step = Steps.INFO_PROMPT
|
||||
set(value) {
|
||||
@ -38,19 +37,27 @@ class ClearAllDataDialog : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
binding = DialogClearAllDataBinding.inflate(LayoutInflater.from(requireContext()))
|
||||
val device = RadioOption("deviceOnly", requireContext().getString(R.string.dialog_clear_all_data_clear_device_only))
|
||||
val network = RadioOption("deviceAndNetwork", requireContext().getString(R.string.dialog_clear_all_data_clear_device_and_network))
|
||||
var selectedOption = device
|
||||
val optionAdapter = RadioOptionAdapter { selectedOption = it }
|
||||
binding.recyclerView.apply {
|
||||
adapter = optionAdapter
|
||||
addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||
setHasFixedSize(true)
|
||||
}
|
||||
optionAdapter.submitList(listOf(device, network))
|
||||
binding.cancelButton.setOnClickListener {
|
||||
if (step == Steps.NETWORK_PROMPT) {
|
||||
clearAllData(false)
|
||||
} else if (step != Steps.DELETING) {
|
||||
dismiss()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
binding.clearAllDataButton.setOnClickListener {
|
||||
when(step) {
|
||||
Steps.INFO_PROMPT -> step = Steps.NETWORK_PROMPT
|
||||
Steps.NETWORK_PROMPT -> {
|
||||
clearAllData(true)
|
||||
Steps.INFO_PROMPT -> if (selectedOption == network) {
|
||||
step = Steps.NETWORK_PROMPT
|
||||
} else {
|
||||
clearAllData(false)
|
||||
}
|
||||
Steps.NETWORK_PROMPT -> clearAllData(true)
|
||||
Steps.DELETING -> { /* do nothing intentionally */ }
|
||||
}
|
||||
}
|
||||
@ -64,17 +71,14 @@ class ClearAllDataDialog : BaseDialog() {
|
||||
|
||||
when (step) {
|
||||
Steps.INFO_PROMPT -> {
|
||||
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_explanation)
|
||||
binding.cancelButton.setText(R.string.cancel)
|
||||
binding.clearAllDataButton.setText(R.string.delete)
|
||||
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_message)
|
||||
}
|
||||
else -> {
|
||||
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_network_explanation)
|
||||
binding.cancelButton.setText(R.string.dialog_clear_all_data_local_only)
|
||||
binding.clearAllDataButton.setText(R.string.dialog_clear_all_data_clear_network)
|
||||
Steps.NETWORK_PROMPT -> {
|
||||
binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_clear_device_and_network_confirmation)
|
||||
}
|
||||
Steps.DELETING -> { /* do nothing intentionally */ }
|
||||
}
|
||||
|
||||
binding.recyclerView.isGone = step == Steps.NETWORK_PROMPT
|
||||
binding.cancelButton.isVisible = !isLoading
|
||||
binding.clearAllDataButton.isVisible = !isLoading
|
||||
binding.progressBar.isVisible = isLoading
|
||||
|
@ -2,9 +2,18 @@ package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
@ -12,19 +21,30 @@ import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
||||
import org.thoughtcrime.securesms.conversation.v2.ViewUtil;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
|
||||
public static final int SINGLE_TYPE = 21;
|
||||
public static final int TOP_TYPE = 22;
|
||||
public static final int MIDDLE_TYPE = 23;
|
||||
public static final int BOTTOM_TYPE = 24;
|
||||
public static final int CATEGORY_TYPE = 25;
|
||||
|
||||
public int horizontalPadding;
|
||||
public int verticalPadding;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
horizontalPadding = ViewUtil.dpToPx(requireContext(), 36);
|
||||
verticalPadding = ViewUtil.dpToPx(requireContext(), 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,6 +53,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
|
||||
|
||||
View lv = getView().findViewById(android.R.id.list);
|
||||
if (lv != null) lv.setPadding(0, 0, 0, 0);
|
||||
setDivider(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -57,17 +78,100 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
|
||||
@SuppressLint("RestrictedApi")
|
||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
return new PreferenceGroupAdapter(preferenceScreen) {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
PreferenceViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
private int getPreferenceType(int position) {
|
||||
Preference preference = getItem(position);
|
||||
if (preference instanceof PreferenceCategory) {
|
||||
return CATEGORY_TYPE;
|
||||
}
|
||||
boolean isStart = isTop(position);
|
||||
boolean isEnd = isBottom(position);
|
||||
if (isStart && isEnd) {
|
||||
// always show full
|
||||
return SINGLE_TYPE;
|
||||
} else {
|
||||
if (isStart) {
|
||||
return TOP_TYPE;
|
||||
} else if (isEnd) {
|
||||
return BOTTOM_TYPE;
|
||||
} else {
|
||||
return MIDDLE_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTop(int position) {
|
||||
if (position == 0) {
|
||||
return true;
|
||||
}
|
||||
Preference previous = getItem(position - 1);
|
||||
return previous instanceof PreferenceCategory;
|
||||
}
|
||||
|
||||
private boolean isBottom(int position) {
|
||||
int size = getItemCount();
|
||||
if (position == size - 1) {
|
||||
// last one
|
||||
return true;
|
||||
}
|
||||
Preference next = getItem(position + 1);
|
||||
return next instanceof PreferenceCategory;
|
||||
}
|
||||
|
||||
public Drawable getBackground(Context context, int position) {
|
||||
int viewType = getPreferenceType(position);
|
||||
Drawable background;
|
||||
switch (viewType) {
|
||||
case SINGLE_TYPE:
|
||||
background = ContextCompat.getDrawable(context, R.drawable.preference_single);
|
||||
break;
|
||||
case TOP_TYPE:
|
||||
background = ContextCompat.getDrawable(context, R.drawable.preference_top);
|
||||
break;
|
||||
case MIDDLE_TYPE:
|
||||
background = ContextCompat.getDrawable(context, R.drawable.preference_middle);
|
||||
break;
|
||||
case BOTTOM_TYPE:
|
||||
background = ContextCompat.getDrawable(context, R.drawable.preference_bottom);
|
||||
break;
|
||||
default:
|
||||
background = null;
|
||||
break;
|
||||
}
|
||||
return background;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
Preference preference = getItem(position);
|
||||
if (preference instanceof PreferenceCategory) {
|
||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
|
||||
layoutParams.topMargin = 0;
|
||||
layoutParams.bottomMargin = 0;
|
||||
holder.itemView.setLayoutParams(layoutParams);
|
||||
setZeroPaddingToLayoutChildren(holder.itemView);
|
||||
} else {
|
||||
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
|
||||
if (iconFrame != null) {
|
||||
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
Drawable background = getBackground(holder.itemView.getContext(), position);
|
||||
holder.itemView.setBackground(background);
|
||||
TextView titleView = holder.itemView.findViewById(android.R.id.title);
|
||||
if (titleView != null) {
|
||||
((TextView) titleView).setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||
}
|
||||
boolean isTop = isTop(position);
|
||||
boolean isBottom = isBottom(position);
|
||||
holder.itemView.setPadding(horizontalPadding, isTop ? verticalPadding : 0, horizontalPadding, isBottom ? verticalPadding : 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,94 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
|
||||
class HelpSettingsActivity: PassphraseRequiredActionBarActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
setContentView(R.layout.activity_fragment_wrapper)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, HelpSettingsFragment())
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
class HelpSettingsFragment: CorrectedPreferenceFragment() {
|
||||
|
||||
companion object {
|
||||
private const val EXPORT_LOGS = "export_logs"
|
||||
private const val TRANSLATE = "translate_session"
|
||||
private const val FEEDBACK = "feedback"
|
||||
private const val FAQ = "faq"
|
||||
private const val SUPPORT = "support"
|
||||
|
||||
private const val CROWDIN_URL = "https://crowdin.com/project/session-android"
|
||||
private const val FEEDBACK_URL = "https://getsession.org/survey"
|
||||
private const val FAQ_URL = "https://getsession.org/faq"
|
||||
private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us"
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_help)
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
||||
preference ?: return false
|
||||
return when (preference.key) {
|
||||
EXPORT_LOGS -> {
|
||||
shareLogs()
|
||||
true
|
||||
}
|
||||
TRANSLATE -> {
|
||||
openLink(CROWDIN_URL)
|
||||
true
|
||||
}
|
||||
FEEDBACK -> {
|
||||
openLink(FEEDBACK_URL)
|
||||
true
|
||||
}
|
||||
FAQ -> {
|
||||
openLink(FAQ_URL)
|
||||
true
|
||||
}
|
||||
SUPPORT -> {
|
||||
openLink(SUPPORT_URL)
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareLogs() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.maxSdkVersion(Build.VERSION_CODES.P)
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied {
|
||||
Toast.makeText(requireActivity(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
.onAllGranted {
|
||||
ShareLogsDialog().show(parentFragmentManager,"Share Logs Dialog")
|
||||
}
|
||||
.execute()
|
||||
}
|
||||
|
||||
private fun openLink(url: String) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(requireActivity(), "Can't open URL", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import network.loki.messenger.databinding.DialogListPreferenceBinding
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
class ListPreferenceDialog(
|
||||
private val listPreference: ListPreference,
|
||||
private val dialogListener: () -> Unit
|
||||
) : BaseDialog() {
|
||||
private lateinit var binding: DialogListPreferenceBinding
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
binding = DialogListPreferenceBinding.inflate(LayoutInflater.from(requireContext()))
|
||||
binding.titleTextView.text = listPreference.dialogTitle
|
||||
binding.messageTextView.text = listPreference.dialogMessage
|
||||
binding.closeButton.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
val options = listPreference.entryValues.zip(listPreference.entries) { value, title ->
|
||||
RadioOption(value.toString(), title.toString())
|
||||
}
|
||||
val valueIndex = listPreference.findIndexOfValue(listPreference.value)
|
||||
val optionAdapter = RadioOptionAdapter(valueIndex) {
|
||||
listPreference.value = it.value
|
||||
dismiss()
|
||||
dialogListener.invoke()
|
||||
}
|
||||
binding.recyclerView.apply {
|
||||
adapter = optionAdapter
|
||||
addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||
setHasFixedSize(true)
|
||||
}
|
||||
optionAdapter.submitList(options)
|
||||
builder.setView(binding.root)
|
||||
builder.setCancelable(false)
|
||||
}
|
||||
|
||||
}
|
@ -4,10 +4,10 @@ package org.thoughtcrime.securesms.preferences;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
|
||||
protected class ListSummaryListener implements Preference.OnPreferenceChangeListener {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -9,20 +11,19 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -42,28 +43,14 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF);
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
ledBlinkPref.setVisible(false);
|
||||
TextSecurePreferences.setNotificationRingtone(getContext(), NotificationChannels.getMessageRingtone(getContext()).toString());
|
||||
TextSecurePreferences.setNotificationVibrateEnabled(getContext(), NotificationChannels.getMessageVibrate(getContext()));
|
||||
|
||||
} else {
|
||||
ledBlinkPref.setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
initializeListSummary((ListPreference) ledBlinkPref);
|
||||
}
|
||||
|
||||
this.findPreference(TextSecurePreferences.LED_COLOR_PREF)
|
||||
.setOnPreferenceChangeListener(new LedColorChangeListener());
|
||||
this.findPreference(TextSecurePreferences.RINGTONE_PREF)
|
||||
.setOnPreferenceChangeListener(new RingtoneSummaryListener());
|
||||
this.findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF)
|
||||
.setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)
|
||||
.setOnPreferenceChangeListener(new NotificationPrivacyListener());
|
||||
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)
|
||||
.setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
this.findPreference(TextSecurePreferences.VIBRATE_PREF)
|
||||
.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
NotificationChannels.updateMessageVibrate(getContext(), (boolean) newValue);
|
||||
@ -86,8 +73,17 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
return true;
|
||||
});
|
||||
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_COLOR_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF));
|
||||
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
ListPreference listPreference = (ListPreference) preference;
|
||||
listPreference.setDialogMessage(R.string.preferences_notifications__content_message);
|
||||
new ListPreferenceDialog(listPreference, () -> {
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
|
||||
return null;
|
||||
}).show(getChildFragmentManager(), "ListPreferenceDialog");
|
||||
return true;
|
||||
});
|
||||
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
@ -99,8 +95,6 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF));
|
||||
}
|
||||
|
||||
initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
@ -183,20 +177,4 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class LedColorChangeListener extends ListSummaryListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
if (NotificationChannels.supported()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
NotificationChannels.updateMessagesLedColor(getActivity(), (String) value);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
return super.onPreferenceChange(preference, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,14 @@ package org.thoughtcrime.securesms.preferences
|
||||
import android.os.Bundle
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment
|
||||
|
||||
class PrivacySettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
setContentView(R.layout.activity_fragment_wrapper)
|
||||
val fragment = AppProtectionPreferenceFragment()
|
||||
val fragment =
|
||||
PrivacySettingsPreferenceFragment()
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
transaction.replace(R.id.fragmentContainer, fragment)
|
||||
transaction.commit()
|
||||
|
@ -12,6 +12,7 @@ import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
@ -23,14 +24,11 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
@ -42,7 +40,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
|
||||
|
||||
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
|
||||
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
|
||||
@ -56,7 +53,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)).setChecked(isEnabled);
|
||||
if (isEnabled && !CallNotificationBuilder.areNotificationsEnabled(requireActivity())) {
|
||||
// show a dialog saying that calls won't work properly if you don't have notifications on at a system level
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
new AlertDialog.Builder(new ContextThemeWrapper(requireActivity(), R.style.ThemeOverlay_Session_AlertDialog))
|
||||
.setTitle(R.string.CallNotificationBuilder_system_notification_title)
|
||||
.setMessage(R.string.CallNotificationBuilder_system_notification_message)
|
||||
.setPositiveButton(R.string.activity_notification_settings_title, (d, w) -> {
|
||||
@ -100,20 +97,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
||||
initializeScreenLockTimeoutSummary();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeScreenLockTimeoutSummary() {
|
||||
long timeoutSeconds = TextSecurePreferences.getScreenLockTimeout(getContext());
|
||||
long hours = TimeUnit.SECONDS.toHours(timeoutSeconds);
|
||||
long minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - (TimeUnit.SECONDS.toHours(timeoutSeconds) * 60 );
|
||||
long seconds = TimeUnit.SECONDS.toSeconds(timeoutSeconds) - (TimeUnit.SECONDS.toMinutes(timeoutSeconds) * 60);
|
||||
|
||||
findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT)
|
||||
.setSummary(timeoutSeconds <= 0 ? getString(R.string.AppProtectionPreferenceFragment_none) :
|
||||
String.format("%02d:%02d:%02d", hours, minutes, seconds));
|
||||
}
|
||||
|
||||
private void initializeVisibility() {
|
||||
@ -143,25 +126,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
}
|
||||
}
|
||||
|
||||
private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
|
||||
if (duration == 0) {
|
||||
TextSecurePreferences.setScreenLockTimeout(getContext(), 0);
|
||||
} else {
|
||||
long timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration);
|
||||
TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
|
||||
}
|
||||
|
||||
initializeScreenLockTimeoutSummary();
|
||||
}, 0).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
@ -215,20 +179,16 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
boolean val = (boolean) newValue;
|
||||
if (val) {
|
||||
// check if we've shown the info dialog and check for microphone permissions
|
||||
if (TextSecurePreferences.setShownCallWarning(context.requireContext())) {
|
||||
new AlertDialog.Builder(context.requireContext())
|
||||
.setTitle(R.string.dialog_voice_video_title)
|
||||
.setMessage(R.string.dialog_voice_video_message)
|
||||
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
|
||||
requestMicrophonePermission();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, w) -> {
|
||||
new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog))
|
||||
.setTitle(R.string.dialog_voice_video_title)
|
||||
.setMessage(R.string.dialog_voice_video_message)
|
||||
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
|
||||
requestMicrophonePermission();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, w) -> {
|
||||
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
requestMicrophonePermission();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
@ -0,0 +1,55 @@
|
||||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ItemSelectableBinding
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
||||
class RadioOptionAdapter(
|
||||
var selectedOptionPosition: Int = 0,
|
||||
private val onClickListener: (RadioOption) -> Unit
|
||||
) : ListAdapter<RadioOption, RadioOptionAdapter.ViewHolder>(RadioOptionDiffer()) {
|
||||
|
||||
class RadioOptionDiffer: DiffUtil.ItemCallback<RadioOption>() {
|
||||
override fun areItemsTheSame(oldItem: RadioOption, newItem: RadioOption) = oldItem === newItem
|
||||
override fun areContentsTheSame(oldItem: RadioOption, newItem: RadioOption) = oldItem == newItem
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_selectable, parent, false)
|
||||
return ViewHolder(itemView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val option = getItem(position)
|
||||
val isSelected = position == selectedOptionPosition
|
||||
holder.bind(option, isSelected) {
|
||||
onClickListener(it)
|
||||
selectedOptionPosition = position
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
val glide = GlideApp.with(itemView)
|
||||
val binding = ItemSelectableBinding.bind(itemView)
|
||||
|
||||
fun bind(option: RadioOption, isSelected: Boolean, toggleSelection: (RadioOption) -> Unit) {
|
||||
binding.titleTextView.text = option.title
|
||||
binding.root.setOnClickListener { toggleSelection(option) }
|
||||
binding.selectButton.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class RadioOption(
|
||||
val value: String,
|
||||
val title: String
|
||||
)
|
@ -30,7 +30,7 @@ class SeedDialog : BaseDialog() {
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val binding = DialogSeedBinding.inflate(LayoutInflater.from(requireContext()))
|
||||
binding.seedTextView.text = seed
|
||||
binding.cancelButton.setOnClickListener { dismiss() }
|
||||
binding.closeButton.setOnClickListener { dismiss() }
|
||||
binding.copyButton.setOnClickListener { copySeed() }
|
||||
builder.setView(binding.root)
|
||||
}
|
||||
|
@ -8,10 +8,11 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
import android.util.SparseArray
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@ -39,11 +40,11 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
|
||||
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
@ -67,6 +68,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
companion object {
|
||||
const val updatedProfileResultCode = 1234
|
||||
private const val SCROLL_STATE = "SCROLL_STATE"
|
||||
}
|
||||
|
||||
// region Lifecycle
|
||||
@ -94,24 +96,31 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
notificationsButton.setOnClickListener { showNotificationSettings() }
|
||||
messageRequestsButton.setOnClickListener { showMessageRequests() }
|
||||
chatsButton.setOnClickListener { showChatSettings() }
|
||||
sendInvitationButton.setOnClickListener { sendInvitation() }
|
||||
faqButton.setOnClickListener { showFAQ() }
|
||||
surveyButton.setOnClickListener { showSurvey() }
|
||||
helpTranslateButton.setOnClickListener { helpTranslate() }
|
||||
appearanceButton.setOnClickListener { showAppearanceSettings() }
|
||||
inviteFriendButton.setOnClickListener { sendInvitation() }
|
||||
helpButton.setOnClickListener { showHelp() }
|
||||
seedButton.setOnClickListener { showSeed() }
|
||||
clearAllDataButton.setOnClickListener { clearAllData() }
|
||||
debugLogButton.setOnClickListener { shareLogs() }
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(this@SettingsActivity)
|
||||
oxenLogoImageView.setImageResource(if (isLightMode) R.drawable.oxen_light_mode else R.drawable.oxen_dark_mode)
|
||||
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val scrollBundle = SparseArray<Parcelable>()
|
||||
binding.scrollView.saveHierarchyState(scrollBundle)
|
||||
outState.putSparseParcelableArray(SCROLL_STATE, scrollBundle)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
savedInstanceState.getSparseParcelableArray<Parcelable>(SCROLL_STATE)?.let { scrollBundle ->
|
||||
binding.scrollView.restoreHierarchyState(scrollBundle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.settings_general, menu)
|
||||
// Update UI mode menu icon
|
||||
val uiMode = UiModeUtilities.getUserSelectedUiMode(this)
|
||||
menu.findItem(R.id.action_change_theme).icon!!.level = uiMode.ordinal
|
||||
return true
|
||||
}
|
||||
|
||||
@ -121,10 +130,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
showQRCode()
|
||||
true
|
||||
}
|
||||
R.id.action_change_theme -> {
|
||||
ChangeUiModeDialog().show(supportFragmentManager, ChangeUiModeDialog.TAG)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@ -295,6 +300,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
push(intent)
|
||||
}
|
||||
|
||||
private fun showAppearanceSettings() {
|
||||
val intent = Intent(this, AppearanceSettingsActivity::class.java)
|
||||
push(intent)
|
||||
}
|
||||
|
||||
private fun sendInvitation() {
|
||||
val intent = Intent()
|
||||
intent.action = Intent.ACTION_SEND
|
||||
@ -305,14 +315,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
startActivity(chooser)
|
||||
}
|
||||
|
||||
private fun showFAQ() {
|
||||
try {
|
||||
val url = "https://getsession.org/faq"
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
private fun showHelp() {
|
||||
val intent = Intent(this, HelpSettingsActivity::class.java)
|
||||
push(intent)
|
||||
}
|
||||
|
||||
private fun showPath() {
|
||||
@ -320,26 +325,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
show(intent)
|
||||
}
|
||||
|
||||
private fun showSurvey() {
|
||||
try {
|
||||
val url = "https://getsession.org/survey"
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun helpTranslate() {
|
||||
try {
|
||||
val url = "https://crowdin.com/project/session-android"
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSeed() {
|
||||
SeedDialog().show(supportFragmentManager, "Recovery Phrase Dialog")
|
||||
}
|
||||
@ -348,20 +333,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
|
||||
}
|
||||
|
||||
private fun shareLogs() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.maxSdkVersion(Build.VERSION_CODES.P)
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied {
|
||||
Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
.onAllGranted {
|
||||
ShareLogsDialog().show(supportFragmentManager,"Share Logs Dialog")
|
||||
}
|
||||
.execute()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
||||
|
@ -0,0 +1,153 @@
|
||||
package org.thoughtcrime.securesms.preferences.appearance
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityAppearanceSettingsBinding
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_LIGHT
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.util.ThemeState
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppearanceSettingsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener {
|
||||
|
||||
companion object {
|
||||
private const val SCROLL_PARCEL = "scroll_parcel"
|
||||
}
|
||||
|
||||
val viewModel: AppearanceSettingsViewModel by viewModels()
|
||||
lateinit var binding : ActivityAppearanceSettingsBinding
|
||||
|
||||
var currentTheme: ThemeState? = null
|
||||
|
||||
private val accentColors
|
||||
get() = mapOf(
|
||||
binding.accentGreen to R.style.PrimaryGreen,
|
||||
binding.accentBlue to R.style.PrimaryBlue,
|
||||
binding.accentYellow to R.style.PrimaryYellow,
|
||||
binding.accentPink to R.style.PrimaryPink,
|
||||
binding.accentPurple to R.style.PrimaryPurple,
|
||||
binding.accentOrange to R.style.PrimaryOrange,
|
||||
binding.accentRed to R.style.PrimaryRed
|
||||
)
|
||||
|
||||
private val themeViews
|
||||
get() = listOf(
|
||||
binding.themeOptionClassicDark,
|
||||
binding.themeRadioClassicDark,
|
||||
binding.themeOptionClassicLight,
|
||||
binding.themeRadioClassicLight,
|
||||
binding.themeOptionOceanDark,
|
||||
binding.themeRadioOceanDark,
|
||||
binding.themeOptionOceanLight,
|
||||
binding.themeRadioOceanLight
|
||||
)
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
v ?: return
|
||||
val accents = accentColors
|
||||
val themes = themeViews
|
||||
if (v in accents) {
|
||||
val entry = accents[v]
|
||||
entry?.let { viewModel.setNewAccent(it) }
|
||||
} else if (v in themes) {
|
||||
val currentBase = if (currentTheme?.theme == R.style.Classic_Dark || currentTheme?.theme == R.style.Classic_Light) R.style.Classic else R.style.Ocean
|
||||
val (mappedStyle, newBase) = when (v) {
|
||||
binding.themeOptionClassicDark, binding.themeRadioClassicDark -> CLASSIC_DARK to R.style.Classic
|
||||
binding.themeOptionClassicLight, binding.themeRadioClassicLight -> CLASSIC_LIGHT to R.style.Classic
|
||||
binding.themeOptionOceanDark, binding.themeRadioOceanDark -> OCEAN_DARK to R.style.Ocean
|
||||
binding.themeOptionOceanLight, binding.themeRadioOceanLight -> OCEAN_LIGHT to R.style.Ocean
|
||||
else -> throw NullPointerException("Invalid style for view [$v]")
|
||||
}
|
||||
viewModel.setNewStyle(mappedStyle)
|
||||
if (currentBase != newBase) {
|
||||
if (newBase == R.style.Ocean) {
|
||||
viewModel.setNewAccent(R.style.PrimaryBlue)
|
||||
} else if (newBase == R.style.Classic) {
|
||||
viewModel.setNewAccent(R.style.PrimaryGreen)
|
||||
}
|
||||
}
|
||||
} else if (v == binding.systemSettingsSwitch) {
|
||||
viewModel.setNewFollowSystemSettings((v as SwitchCompat).isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val scrollParcelArray = SparseArray<Parcelable>()
|
||||
binding.scrollView.saveHierarchyState(scrollParcelArray)
|
||||
outState.putSparseParcelableArray(SCROLL_PARCEL, scrollParcelArray)
|
||||
}
|
||||
|
||||
private fun updateSelectedTheme(themeStyle: Int) {
|
||||
mapOf(
|
||||
R.style.Classic_Dark to binding.themeRadioClassicDark,
|
||||
R.style.Classic_Light to binding.themeRadioClassicLight,
|
||||
R.style.Ocean_Dark to binding.themeRadioOceanDark,
|
||||
R.style.Ocean_Light to binding.themeRadioOceanLight
|
||||
).forEach { (style, view) ->
|
||||
view.isChecked = themeStyle == style
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSelectedAccent(accentStyle: Int) {
|
||||
accentColors.forEach { (view, style) ->
|
||||
view.isSelected = style == accentStyle
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFollowSystemToggle(followSystemSettings: Boolean) {
|
||||
binding.systemSettingsSwitch.isChecked = followSystemSettings
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
binding = ActivityAppearanceSettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
savedInstanceState?.let { bundle ->
|
||||
val scrollStateParcel = bundle.getSparseParcelableArray<Parcelable>(SCROLL_PARCEL)
|
||||
if (scrollStateParcel != null) {
|
||||
binding.scrollView.restoreHierarchyState(scrollStateParcel)
|
||||
}
|
||||
}
|
||||
supportActionBar!!.title = getString(R.string.activity_settings_message_appearance_button_title)
|
||||
with (binding) {
|
||||
// accent toggles
|
||||
accentContainer.children.forEach { view ->
|
||||
view.setOnClickListener(this@AppearanceSettingsActivity)
|
||||
}
|
||||
// theme toggles
|
||||
themeViews.forEach {
|
||||
it.setOnClickListener(this@AppearanceSettingsActivity)
|
||||
}
|
||||
// system settings toggle
|
||||
systemSettingsSwitch.setOnClickListener(this@AppearanceSettingsActivity)
|
||||
}
|
||||
|
||||
lifecycleScope.launchWhenResumed {
|
||||
viewModel.uiState.collectLatest { themeState ->
|
||||
val (theme, accent, followSystem) = themeState
|
||||
updateSelectedTheme(theme)
|
||||
updateSelectedAccent(accent)
|
||||
updateFollowSystemToggle(followSystem)
|
||||
if (currentTheme != null && currentTheme != themeState) {
|
||||
recreate()
|
||||
} else {
|
||||
currentTheme = themeState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package org.thoughtcrime.securesms.preferences.appearance
|
||||
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.ThemeState
|
||||
import org.thoughtcrime.securesms.util.themeState
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSecurePreferences) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(prefs.themeState())
|
||||
val uiState: StateFlow<ThemeState> = _uiState
|
||||
|
||||
fun setNewAccent(@StyleRes newAccentColorStyle: Int) {
|
||||
prefs.setAccentColorStyle(newAccentColorStyle)
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
}
|
||||
|
||||
fun setNewStyle(newThemeStyle: String) {
|
||||
prefs.setThemeStyle(newThemeStyle)
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
}
|
||||
|
||||
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
||||
prefs.setFollowSystemSettings(followSystemSettings)
|
||||
_uiState.value = prefs.themeState()
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2017 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
/**
|
||||
* List preference that disables dependents when set to "none", similar to a CheckBoxPreference.
|
||||
*
|
||||
* @author Taylor Kline
|
||||
*/
|
||||
|
||||
public class LEDColorListPreference extends ListPreference {
|
||||
|
||||
private static final String TAG = LEDColorListPreference.class.getSimpleName();
|
||||
|
||||
private ImageView colorImageView;
|
||||
|
||||
public LEDColorListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setWidgetLayoutResource(R.layout.led_color_preference_widget);
|
||||
}
|
||||
|
||||
public LEDColorListPreference(Context context) {
|
||||
super(context);
|
||||
setWidgetLayoutResource(R.layout.led_color_preference_widget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
CharSequence oldEntry = getEntry();
|
||||
super.setValue(value);
|
||||
CharSequence newEntry = getEntry();
|
||||
if (oldEntry != newEntry) {
|
||||
notifyDependencyChange(shouldDisableDependents());
|
||||
}
|
||||
|
||||
if (value != null) setPreviewColor(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDisableDependents() {
|
||||
CharSequence newEntry = getValue();
|
||||
boolean shouldDisable = newEntry.equals("none");
|
||||
return shouldDisable || super.shouldDisableDependents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
this.colorImageView = (ImageView)view.findViewById(R.id.color_view);
|
||||
setPreviewColor(getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
super.setSummary(null);
|
||||
}
|
||||
|
||||
private void setPreviewColor(@NonNull String value) {
|
||||
int color;
|
||||
|
||||
switch (value) {
|
||||
case "green": color = getContext().getResources().getColor(R.color.green_500); break;
|
||||
case "red": color = getContext().getResources().getColor(R.color.red_500); break;
|
||||
case "blue": color = getContext().getResources().getColor(R.color.blue_500); break;
|
||||
case "yellow": color = getContext().getResources().getColor(R.color.yellow_500); break;
|
||||
case "cyan": color = getContext().getResources().getColor(R.color.cyan_500); break;
|
||||
case "magenta": color = getContext().getResources().getColor(R.color.pink_500); break;
|
||||
case "white": color = getContext().getResources().getColor(R.color.white); break;
|
||||
default: color = getContext().getResources().getColor(R.color.transparent); break;
|
||||
}
|
||||
|
||||
if (colorImageView != null) {
|
||||
GradientDrawable drawable = new GradientDrawable();
|
||||
drawable.setShape(GradientDrawable.OVAL);
|
||||
drawable.setColor(color);
|
||||
|
||||
colorImageView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.preferences.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
|
||||
class NotificationSettingsPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
// TODO: if we want do the spans
|
||||
}
|
||||
|
||||
}
|
@ -68,20 +68,14 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
if (ThemeUtil.isDarkTheme(requireContext())) {
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_TextSecure_BottomSheetDialog_Fixed_ReactWithAny);
|
||||
} else {
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_TextSecure_Light_BottomSheetDialog_Fixed_ReactWithAny);
|
||||
}
|
||||
|
||||
// setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Session_BottomSheet);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.reactions_bottom_sheet_dialog_fragment, container, false);
|
||||
}
|
||||
|
||||
@ -126,7 +120,7 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
|
||||
View customView = tab.getCustomView();
|
||||
TextView text = customView.findViewById(R.id.reactions_pill_count);
|
||||
customView.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.reaction_pill_background_selected));
|
||||
text.setTextColor(ContextCompat.getColor(requireContext(), R.color.reactions_pill_selected_text_color));
|
||||
text.setTextColor(ThemeUtil.getThemedColor(requireContext(), R.attr.reactionsPillSelectedTextColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -134,7 +128,7 @@ public final class ReactionsDialogFragment extends BottomSheetDialogFragment imp
|
||||
View customView = tab.getCustomView();
|
||||
TextView text = customView.findViewById(R.id.reactions_pill_count);
|
||||
customView.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.reaction_pill_dialog_background));
|
||||
text.setTextColor(ContextCompat.getColor(requireContext(), R.color.reactions_pill_text_color));
|
||||
text.setTextColor(ThemeUtil.getThemedColor(requireContext(), R.attr.reactionsPillNormalTextColor));
|
||||
}
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {}
|
||||
|
@ -14,8 +14,6 @@ import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
@ -24,7 +22,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.android.material.shape.CornerFamily;
|
||||
import com.google.android.material.shape.MaterialShapeDrawable;
|
||||
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
|
||||
@ -80,7 +77,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Widget_TextSecure_ReactWithAny);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,22 +89,6 @@ public final class ReactWithAnyEmojiDialogFragment extends BottomSheetDialogFrag
|
||||
.setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 18))
|
||||
.build();
|
||||
|
||||
MaterialShapeDrawable dialogBackground = new MaterialShapeDrawable(shapeAppearanceModel);
|
||||
|
||||
dialogBackground.setTint(ContextCompat.getColor(requireContext(), R.color.react_with_any_background));
|
||||
|
||||
dialog.getBehavior().addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
if (bottomSheet.getBackground() != dialogBackground) {
|
||||
ViewCompat.setBackground(bottomSheet, dialogBackground);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
|
||||
});
|
||||
|
||||
boolean shadows = requireArguments().getBoolean(ARG_SHADOWS, true);
|
||||
if (!shadows) {
|
||||
Window window = dialog.getWindow();
|
||||
|
@ -1,14 +1,14 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
@ -67,4 +67,35 @@ interface ActivityDispatcher {
|
||||
}
|
||||
fun dispatchIntent(body: (Context)->Intent?)
|
||||
fun showDialog(baseDialog: BaseDialog, tag: String? = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun TextSecurePreferences.themeState(): ThemeState {
|
||||
val themeStyle = getThemeStyle().getThemeStyle()
|
||||
val accentStyle = getAccentColorStyle() ?: themeStyle.getDefaultAccentColor()
|
||||
val followSystem = getFollowSystemSettings()
|
||||
return ThemeState(
|
||||
themeStyle,
|
||||
accentStyle,
|
||||
followSystem
|
||||
)
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
fun String.getThemeStyle(): Int = when (this) {
|
||||
TextSecurePreferences.CLASSIC_DARK -> R.style.Classic_Dark
|
||||
TextSecurePreferences.CLASSIC_LIGHT -> R.style.Classic_Light
|
||||
TextSecurePreferences.OCEAN_DARK -> R.style.Ocean_Dark
|
||||
TextSecurePreferences.OCEAN_LIGHT -> R.style.Ocean_Light
|
||||
else -> throw NullPointerException("The style [$this] is not supported")
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
fun Int.getDefaultAccentColor(): Int =
|
||||
if (this == R.style.Ocean_Dark || this == R.style.Ocean_Light) R.style.PrimaryBlue
|
||||
else R.style.PrimaryGreen
|
||||
|
||||
data class ThemeState (
|
||||
@StyleRes val theme: Int,
|
||||
@StyleRes val accentStyle: Int,
|
||||
val followSystem: Boolean
|
||||
)
|
@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.util
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
@ -11,8 +12,6 @@ import android.widget.RelativeLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
interface GlowView {
|
||||
@ -22,7 +21,7 @@ interface GlowView {
|
||||
|
||||
object GlowViewUtilities {
|
||||
|
||||
fun animateColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
fun animateColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = context.resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = context.resources.getColorWithID(endColorID, context.theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
@ -34,7 +33,17 @@ object GlowViewUtilities {
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun animateShadowColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
fun animateColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
view.mainColor = color
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun animateShadowColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = context.resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = context.resources.getColorWithID(endColorID, context.theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
@ -45,6 +54,17 @@ object GlowViewUtilities {
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun animateShadowColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
view.sessionShadowColor = color
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PNModeView : LinearLayout, GlowView {
|
||||
|
@ -2,46 +2,12 @@ package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.PreferenceManager
|
||||
import network.loki.messenger.R
|
||||
|
||||
/**
|
||||
* Day/night UI mode related utilities.
|
||||
* @see <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme">Official Documentation</a>
|
||||
*/
|
||||
object UiModeUtilities {
|
||||
private const val PREF_KEY_SELECTED_UI_MODE = "SELECTED_UI_MODE"
|
||||
|
||||
@JvmStatic
|
||||
fun setUserSelectedUiMode(context: Context, uiMode: UiMode) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit()
|
||||
.putString(PREF_KEY_SELECTED_UI_MODE, uiMode.name)
|
||||
.apply()
|
||||
AppCompatDelegate.setDefaultNightMode(uiMode.nightModeValue)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getUserSelectedUiMode(context: Context): UiMode {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val selectedUiModeName = prefs.getString(PREF_KEY_SELECTED_UI_MODE, UiMode.SYSTEM_DEFAULT.name)!!
|
||||
var selectedUiMode: UiMode
|
||||
try {
|
||||
selectedUiMode = UiMode.valueOf(selectedUiModeName)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Cannot recognize UiMode constant from the given string.
|
||||
selectedUiMode = UiMode.SYSTEM_DEFAULT
|
||||
}
|
||||
return selectedUiMode
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setupUiModeToUserSelected(context: Context) {
|
||||
val selectedUiMode = getUserSelectedUiMode(context)
|
||||
setUserSelectedUiMode(context, selectedUiMode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the application UI is in the light mode
|
||||
@ -52,14 +18,5 @@ object UiModeUtilities {
|
||||
val uiModeNightBit = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
return uiModeNightBit == Configuration.UI_MODE_NIGHT_NO
|
||||
}
|
||||
}
|
||||
|
||||
enum class UiMode(
|
||||
@StringRes
|
||||
val displayNameRes: Int,
|
||||
val nightModeValue: Int) {
|
||||
|
||||
DAY(R.string.dialog_ui_mode_option_day, AppCompatDelegate.MODE_NIGHT_NO),
|
||||
NIGHT(R.string.dialog_ui_mode_option_night, AppCompatDelegate.MODE_NIGHT_YES),
|
||||
SYSTEM_DEFAULT(R.string.dialog_ui_mode_option_system_default, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
@ -7,8 +7,11 @@ import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.DimenRes
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DimenRes
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
|
||||
fun View.contains(point: PointF): Boolean {
|
||||
@ -22,6 +25,9 @@ val View.hitRect: Rect
|
||||
return rect
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun Context.getAccentColor() = getColorFromAttr(R.attr.colorAccent)
|
||||
|
||||
fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int, animationDuration: Long = 250) {
|
||||
val startSize = resources.getDimension(startSizeID)
|
||||
val endSize = resources.getDimension(endSizeID)
|
||||
|
5
app/src/main/res/color/emoji_tab_text_color.xml
Normal file
5
app/src/main/res/color/emoji_tab_text_color.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -3,6 +3,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid android:color="@color/accent" />
|
||||
<solid android:color="?colorAccent" />
|
||||
|
||||
</shape>
|
@ -4,6 +4,6 @@
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="@dimen/context_menu_corner_radius" />
|
||||
<solid android:color="@color/background_dialog" />
|
||||
<solid android:color="?colorPrimary" />
|
||||
|
||||
</shape>
|
@ -4,7 +4,7 @@
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
|
||||
<item>
|
||||
<selector>
|
||||
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
|
||||
<item android:drawable="?colorAccent" android:state_selected="true" />
|
||||
</selector>
|
||||
</item>
|
||||
</ripple>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
|
||||
<item>
|
||||
<selector>
|
||||
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
|
||||
<item android:drawable="?colorAccent" android:state_selected="true" />
|
||||
</selector>
|
||||
</item>
|
||||
</ripple>
|
||||
|
6
app/src/main/res/drawable/conversation_menu_divider.xml
Normal file
6
app/src/main/res/drawable/conversation_menu_divider.xml
Normal 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>
|
8
app/src/main/res/drawable/conversation_menu_gradient.xml
Normal file
8
app/src/main/res/drawable/conversation_menu_gradient.xml
Normal 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>
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/cell_selected">
|
||||
android:color="?colorCellRipple">
|
||||
|
||||
<item>
|
||||
<color android:color="?attr/conversation_pinned_background_color" />
|
||||
<color android:color="?conversation_pinned_background_color" />
|
||||
</item>
|
||||
</ripple>
|
@ -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>
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/cell_selected">
|
||||
android:color="?colorCellRipple">
|
||||
|
||||
<item>
|
||||
<color android:color="@color/cell_background" />
|
||||
<color android:color="?colorCellBackground" />
|
||||
</item>
|
||||
</ripple>
|
@ -3,11 +3,11 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="?attr/dialog_background_color" />
|
||||
<solid android:color="?dialog_background_color" />
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/dialog_corner_radius"
|
||||
android:topRightRadius="@dimen/dialog_corner_radius" />
|
||||
|
||||
<!-- <stroke android:width="@dimen/border_thickness" android:color="@color/dialog_border" />-->
|
||||
<stroke android:width="@dimen/border_thickness" android:color="?dialog_border" />
|
||||
</shape>
|
@ -5,6 +5,6 @@
|
||||
|
||||
<solid android:color="?attr/dialog_background_color" />
|
||||
|
||||
<corners android:radius="@dimen/dialog_corner_radius" />
|
||||
<corners android:radius="?dialogCornerRadius" />
|
||||
|
||||
</shape>
|
@ -5,8 +5,8 @@
|
||||
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:startColor="@color/default_background_start"
|
||||
android:endColor="@color/default_background_end"
|
||||
android:startColor="?default_background_start"
|
||||
android:endColor="?default_background_end"
|
||||
android:type="linear" />
|
||||
|
||||
</shape>
|
@ -5,7 +5,7 @@
|
||||
|
||||
<solid android:color="@color/transparent" />
|
||||
|
||||
<corners android:radius="@dimen/medium_button_corner_radius" />
|
||||
<corners android:radius="@dimen/dialog_button_corner_radius" />
|
||||
|
||||
<stroke android:width="@dimen/border_thickness" android:color="?android:textColorPrimary" />
|
||||
<stroke android:width="@dimen/border_thickness" android:color="@color/transparent" />
|
||||
</shape>
|
16
app/src/main/res/drawable/ic_appearance.xml
Normal file
16
app/src/main/res/drawable/ic_appearance.xml
Normal 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>
|
@ -3,7 +3,7 @@
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
android:tint="?colorAccent">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="m12,2.4355c-5.2796,0 -9.5645,4.2848 -9.5645,9.5645 0,5.2796 4.2848,9.5645 9.5645,9.5645 5.2796,0 9.5645,-4.2848 9.5645,-9.5645 0,-5.2796 -4.2848,-9.5645 -9.5645,-9.5645zM12.123,7.9375 L15.6777,11.4922 14.9707,12.1992 12.623,9.8515v6.1797h-1v-6.1797l-1.9961,1.9941 -0.3535,0.3535 -0.707,-0.707 0.3535,-0.3535 3.2031,-3.2012z"
|
||||
|
@ -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>
|
@ -2,8 +2,7 @@
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
|
11
app/src/main/res/drawable/ic_clear_data.xml
Normal file
11
app/src/main/res/drawable/ic_clear_data.xml
Normal 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>
|
9
app/src/main/res/drawable/ic_conversations.xml
Normal file
9
app/src/main/res/drawable/ic_conversations.xml
Normal 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
Loading…
Reference in New Issue
Block a user