diff --git a/app/build.gradle b/app/build.gradle index 3e85345768..3659bea02e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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, diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eddee7e844..681fc00c17 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -140,6 +140,12 @@ android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity" android:screenOrientation="portrait" android:theme="@style/Theme.Session.DayNight.FlatActionBar" /> + + + + android:theme="@style/Theme.Session.DayNight"> + android:theme="@style/Theme.Session.DayNight" /> + diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index dad5b09288..01bc1f38ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java index d969dcb704..4280607ea2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java @@ -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); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt new file mode 100644 index 0000000000..94c7517eb0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt @@ -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() + + 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() + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 4797bb3edd..0ded9f346e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java deleted file mode 100644 index 6ffb3dde97..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ /dev/null @@ -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 audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList(); - List documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList(); - List imageSlides = Stream.of(attachments.getSlides()).filter(Slide::hasImage).limit(1).toList(); - List 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 imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList(); - List 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 getAttachments() { - return attachments.asAttachments(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java b/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java index bdfad8c63a..6e7993a575 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index 88d8787e88..e88cf1d08b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt index ee255a2721..7b666be56f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationFragment.kt @@ -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 = diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt index f8936d581f..2e62932ab0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index e5815d974f..7fe583ddbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -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() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index affabd3f5a..7ac70b843a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt index abf6720aa0..c88e05dbfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarButton.kt @@ -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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index c5f12650f7..663dd2e255 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -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(R.id.menu_badge_icon) val badgeView = actionView.findViewById(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) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java index 54a8e7626c..6d16f1f421 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java @@ -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)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index d0b9a78226..cb6bb536ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -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]) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/OpenGroupInvitationView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/OpenGroupInvitationView.kt index 3fbc1e6ac0..fc9a46228e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/OpenGroupInvitationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/OpenGroupInvitationView.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index a08bef5a55..91ab4c106d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -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) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 7a00830ef3..88045f8e47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -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(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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index bd68693005..0b4c1455fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt index eccb74b127..1ae2902188 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt index 11b2c6f6d1..1ba4a0c3e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt @@ -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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailProgressBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailProgressBar.kt index fd9a7c8404..3abfd235ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailProgressBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailProgressBar.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index b984a6e0f2..ce950214f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -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)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java index 0570d91503..0f4d7c9f25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseContentProviders.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 2834eb341d..58693172ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -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 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 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 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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 508cde51a3..62c79674f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -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) { + val recipientDb = DatabaseComponent.get(context).recipientDatabase() + recipientDb.setBlocked(toUnblock, false) + } + + override fun blockedContacts(): List { + val recipientDb = DatabaseComponent.get(context).recipientDatabase() + return recipientDb.blockedContacts + } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 372f142f99..6ce69a591a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/PlaceholderAvatarLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/PlaceholderAvatarLoader.kt index ca78b572c5..69c9b8c4f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/PlaceholderAvatarLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/PlaceholderAvatarLoader.kt @@ -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 { +class PlaceholderAvatarLoader(): ModelLoader { override fun buildLoadData( model: PlaceholderAvatarPhoto, @@ -17,14 +16,14 @@ class PlaceholderAvatarLoader(private val context: Context): ModelLoader { - 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 { + class Factory() : ModelLoaderFactory { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { - return PlaceholderAvatarLoader(context) + return PlaceholderAvatarLoader() } override fun teardown() {} } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index c195525ac0..ead979b773 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index 01a1a3241e..0b3c44a548 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 433d17f675..bfa9b14489 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 429bbd39f1..2544b2a1ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index 23ea811c6b..fcaf565e0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt index c76668c418..2922044435 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/PathStatusView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/PathStatusView.kt index 85d54a977f..947bd89b4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/PathStatusView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/PathStatusView.kt @@ -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 } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index 37e4a90dd8..f3915abff6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt index 13a4eb6ff0..07da14b090 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt index 5e1895b96d..fac0a402e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java index 02172b7248..0a24c26fad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -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()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index 549d6eac0a..64617eb6a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 61c126339d..8bf1d7d8d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index b461081cfe..7925b8556a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index c081e8fa90..92583d89e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -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 -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/SeedActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/SeedActivity.kt index b179ce852c..0eab58fa0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/SeedActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/SeedActivity.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java b/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java index f2adf32cd3..999dad001d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java @@ -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))) diff --git a/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.java b/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.java index e1d1d192bb..a346d591ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt new file mode 100644 index 0000000000..504194d3a4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt @@ -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) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt new file mode 100644 index 0000000000..50af49b557 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -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(RecipientDiffer()) { + + class RecipientDiffer: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem === newItem + override fun areContentsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem == newItem + } + + private val selectedItems = mutableListOf() + + 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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsLayout.kt new file mode 100644 index 0000000000..ed2970fbc4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsLayout.kt @@ -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) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt new file mode 100644 index 0000000000..254d34978d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt new file mode 100644 index 0000000000..9c0a436ebb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -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(capacity = Channel.CONFLATED) + + private val _contacts = MutableLiveData(BlockedContactsViewState(emptyList())) + + fun subscribe(context: Context): LiveData { + 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) { + storage.unblock(toUnblock) + } + + data class BlockedContactsViewState( + val blockedContacts: List + ) + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChangeUiModeDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChangeUiModeDialog.kt index 594d1b47f7..3d5b9e2e99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChangeUiModeDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChangeUiModeDialog.kt @@ -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() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt index 67faa9da46..6852d2f63f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt index d4d30d864b..560c137104 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java index 7bf9672374..badcbe66b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java @@ -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); } } }; diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt new file mode 100644 index 0000000000..90ffbd4b13 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -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() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ListPreferenceDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListPreferenceDialog.kt new file mode 100644 index 0000000000..e407c67774 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListPreferenceDialog.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java index 4b330459e6..4314b9ae62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 6ceb050693..9ae78fc5cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -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() { - @Override - protected Void doInBackground(Void... voids) { - NotificationChannels.updateMessagesLedColor(getActivity(), (String) value); - return null; - } - }.execute(); - } - return super.onPreferenceChange(preference, value); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt index bf277dc17f..b8606a3d54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java similarity index 75% rename from app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java rename to app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java index 5c42f38bcd..b5774447ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt new file mode 100644 index 0000000000..2cb61a0e82 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt @@ -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(RadioOptionDiffer()) { + + class RadioOptionDiffer: DiffUtil.ItemCallback() { + 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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt index 32624129dd..e7bfd60d3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt @@ -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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 934b9e4ea5..0a56c5058d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -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() + binding.scrollView.saveHierarchyState(scrollBundle) + outState.putSparseParcelableArray(SCROLL_STATE, scrollBundle) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + savedInstanceState.getSparseParcelableArray(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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt new file mode 100644 index 0000000000..bfeea554ee --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt @@ -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() + 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(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 + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsViewModel.kt new file mode 100644 index 0000000000..c569441382 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsViewModel.kt @@ -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 = _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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java deleted file mode 100644 index 075df3a026..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java +++ /dev/null @@ -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 . - */ -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); - } - } - - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/NotificationSettingsPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/NotificationSettingsPreference.kt new file mode 100644 index 0000000000..3c2e72779d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/NotificationSettingsPreference.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java index c327da03ba..0d4afb5911 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDialogFragment.java @@ -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) {} diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java index 3c8661fd82..2fd547eab3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiDialogFragment.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt index d5daba5872..d5b361ecd6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt @@ -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) -} \ No newline at end of file +} + +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 +) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GlowView.kt b/app/src/main/java/org/thoughtcrime/securesms/util/GlowView.kt index 16151fd6c4..08b81e5cb7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GlowView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GlowView.kt @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UiModeUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/UiModeUtilities.kt index 39df0fccca..8b716c8e20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/UiModeUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/UiModeUtilities.kt @@ -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 Official Documentation */ 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); } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt index a02033b862..7b7f3a04f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt @@ -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) diff --git a/app/src/main/res/color/emoji_tab_text_color.xml b/app/src/main/res/color/emoji_tab_text_color.xml new file mode 100644 index 0000000000..024d8d366c --- /dev/null +++ b/app/src/main/res/color/emoji_tab_text_color.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable-notnight/reaction_pill_background.xml b/app/src/main/res/drawable-notnight/reaction_pill_background.xml deleted file mode 100644 index 3ff042175a..0000000000 --- a/app/src/main/res/drawable-notnight/reaction_pill_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable-notnight/reaction_pill_background_selected.xml b/app/src/main/res/drawable-notnight/reaction_pill_background_selected.xml deleted file mode 100644 index 2c735fb23d..0000000000 --- a/app/src/main/res/drawable-notnight/reaction_pill_background_selected.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable-notnight/reaction_pill_dialog_background.xml b/app/src/main/res/drawable-notnight/reaction_pill_dialog_background.xml deleted file mode 100644 index 2d9fa321b8..0000000000 --- a/app/src/main/res/drawable-notnight/reaction_pill_dialog_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable-notnight/unimportant_filled_button_medium_background.xml b/app/src/main/res/drawable-notnight/unimportant_filled_button_medium_background.xml deleted file mode 100644 index f6ea907be5..0000000000 --- a/app/src/main/res/drawable-notnight/unimportant_filled_button_medium_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/accent_dot.xml b/app/src/main/res/drawable/accent_dot.xml index 2d5ffdfe0c..4506871b47 100644 --- a/app/src/main/res/drawable/accent_dot.xml +++ b/app/src/main/res/drawable/accent_dot.xml @@ -3,6 +3,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/context_menu_background.xml b/app/src/main/res/drawable/context_menu_background.xml index e5a1ec0dd8..3691575836 100644 --- a/app/src/main/res/drawable/context_menu_background.xml +++ b/app/src/main/res/drawable/context_menu_background.xml @@ -4,6 +4,6 @@ android:shape="rectangle"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_list_item_background.xml b/app/src/main/res/drawable/conversation_list_item_background.xml index bcc1673828..2e3818f7e3 100644 --- a/app/src/main/res/drawable/conversation_list_item_background.xml +++ b/app/src/main/res/drawable/conversation_list_item_background.xml @@ -4,7 +4,7 @@ - + diff --git a/app/src/main/res/drawable/conversation_list_item_background_dark.xml b/app/src/main/res/drawable/conversation_list_item_background_dark.xml index bcc1673828..2e3818f7e3 100644 --- a/app/src/main/res/drawable/conversation_list_item_background_dark.xml +++ b/app/src/main/res/drawable/conversation_list_item_background_dark.xml @@ -4,7 +4,7 @@ - + diff --git a/app/src/main/res/drawable/conversation_menu_divider.xml b/app/src/main/res/drawable/conversation_menu_divider.xml new file mode 100644 index 0000000000..f6c8a9a710 --- /dev/null +++ b/app/src/main/res/drawable/conversation_menu_divider.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_menu_gradient.xml b/app/src/main/res/drawable/conversation_menu_gradient.xml new file mode 100644 index 0000000000..04862a87ac --- /dev/null +++ b/app/src/main/res/drawable/conversation_menu_gradient.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_pinned_background.xml b/app/src/main/res/drawable/conversation_pinned_background.xml index 9a435d05f3..104b9c272e 100644 --- a/app/src/main/res/drawable/conversation_pinned_background.xml +++ b/app/src/main/res/drawable/conversation_pinned_background.xml @@ -1,9 +1,9 @@ + android:color="?colorCellRipple"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_unread_background.xml b/app/src/main/res/drawable/conversation_unread_background.xml new file mode 100644 index 0000000000..de0f5fb688 --- /dev/null +++ b/app/src/main/res/drawable/conversation_unread_background.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_view_background.xml b/app/src/main/res/drawable/conversation_view_background.xml index ee5f321c99..aaceb7ed54 100644 --- a/app/src/main/res/drawable/conversation_view_background.xml +++ b/app/src/main/res/drawable/conversation_view_background.xml @@ -1,9 +1,9 @@ + android:color="?colorCellRipple"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/default_bottom_sheet_background.xml b/app/src/main/res/drawable/default_bottom_sheet_background.xml index 4b8cb781e3..63532b0d05 100644 --- a/app/src/main/res/drawable/default_bottom_sheet_background.xml +++ b/app/src/main/res/drawable/default_bottom_sheet_background.xml @@ -3,11 +3,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/default_dialog_background.xml b/app/src/main/res/drawable/default_dialog_background.xml index 4cd206aca8..00953c88a4 100644 --- a/app/src/main/res/drawable/default_dialog_background.xml +++ b/app/src/main/res/drawable/default_dialog_background.xml @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/default_session_background.xml b/app/src/main/res/drawable/default_session_background.xml index d397981ab5..e961e985e6 100644 --- a/app/src/main/res/drawable/default_session_background.xml +++ b/app/src/main/res/drawable/default_session_background.xml @@ -5,8 +5,8 @@ \ No newline at end of file diff --git a/app/src/main/res/drawable-notnight/prominent_outline_button_medium_background.xml b/app/src/main/res/drawable/destructive_dialog_text_button_background.xml similarity index 73% rename from app/src/main/res/drawable-notnight/prominent_outline_button_medium_background.xml rename to app/src/main/res/drawable/destructive_dialog_text_button_background.xml index 6e0de35a5b..1eff84a69b 100644 --- a/app/src/main/res/drawable-notnight/prominent_outline_button_medium_background.xml +++ b/app/src/main/res/drawable/destructive_dialog_text_button_background.xml @@ -5,7 +5,7 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_appearance.xml b/app/src/main/res/drawable/ic_appearance.xml new file mode 100644 index 0000000000..bda7bf3550 --- /dev/null +++ b/app/src/main/res/drawable/ic_appearance.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_up_circle_24.xml b/app/src/main/res/drawable/ic_arrow_up_circle_24.xml index a13b7c5b40..fc53bc0971 100644 --- a/app/src/main/res/drawable/ic_arrow_up_circle_24.xml +++ b/app/src/main/res/drawable/ic_arrow_up_circle_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="?colorAccent"> + + diff --git a/app/src/main/res/drawable/ic_baseline_clear_24.xml b/app/src/main/res/drawable/ic_baseline_clear_24.xml index 16d6d37dd9..a20716a20f 100644 --- a/app/src/main/res/drawable/ic_baseline_clear_24.xml +++ b/app/src/main/res/drawable/ic_baseline_clear_24.xml @@ -2,8 +2,7 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_clear_data.xml b/app/src/main/res/drawable/ic_clear_data.xml new file mode 100644 index 0000000000..320015bb23 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_data.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_conversations.xml b/app/src/main/res/drawable/ic_conversations.xml new file mode 100644 index 0000000000..a32d5ec910 --- /dev/null +++ b/app/src/main/res/drawable/ic_conversations.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_external.xml b/app/src/main/res/drawable/ic_external.xml new file mode 100644 index 0000000000..fb4803977c --- /dev/null +++ b/app/src/main/res/drawable/ic_external.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 0000000000..670a82c658 --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_invite_friend.xml b/app/src/main/res/drawable/ic_invite_friend.xml new file mode 100644 index 0000000000..e46d22704e --- /dev/null +++ b/app/src/main/res/drawable/ic_invite_friend.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml index 56b2c2168c..9e20c99e12 100644 --- a/app/src/main/res/drawable/ic_link.xml +++ b/app/src/main/res/drawable/ic_link.xml @@ -5,5 +5,5 @@ android:viewportHeight="512"> + android:fillColor="?android:textColorPrimary"/> diff --git a/app/src/main/res/drawable/ic_message_requests.xml b/app/src/main/res/drawable/ic_message_requests.xml new file mode 100644 index 0000000000..de8e1a6908 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_requests.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml index 390fe36f6b..d241d605b1 100644 --- a/app/src/main/res/drawable/ic_pin.xml +++ b/app/src/main/res/drawable/ic_pin.xml @@ -1,9 +1,9 @@ + android:viewportHeight="122.867"> diff --git a/app/src/main/res/drawable/ic_privacy_icon.xml b/app/src/main/res/drawable/ic_privacy_icon.xml new file mode 100644 index 0000000000..4162beb8d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_icon.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_recovery_phrase.xml b/app/src/main/res/drawable/ic_recovery_phrase.xml new file mode 100644 index 0000000000..3d5bc18e21 --- /dev/null +++ b/app/src/main/res/drawable/ic_recovery_phrase.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_speaker.xml b/app/src/main/res/drawable/ic_speaker.xml new file mode 100644 index 0000000000..505bf988b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_speaker.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_themepreview.xml b/app/src/main/res/drawable/ic_themepreview.xml new file mode 100644 index 0000000000..ce32d94a33 --- /dev/null +++ b/app/src/main/res/drawable/ic_themepreview.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/mediarail_media_outline.xml b/app/src/main/res/drawable/mediarail_media_outline.xml index 6b6f5a68a2..6fa4008748 100644 --- a/app/src/main/res/drawable/mediarail_media_outline.xml +++ b/app/src/main/res/drawable/mediarail_media_outline.xml @@ -4,5 +4,5 @@ + android:color="?colorAccent"/> \ No newline at end of file diff --git a/app/src/main/res/drawable/mention_candidate_view_background.xml b/app/src/main/res/drawable/mention_candidate_view_background.xml index 1b30b3e72e..7b179020aa 100644 --- a/app/src/main/res/drawable/mention_candidate_view_background.xml +++ b/app/src/main/res/drawable/mention_candidate_view_background.xml @@ -1,9 +1,9 @@ + android:color="?mention_candidates_view_background_ripple"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/padded_circle_accent.xml b/app/src/main/res/drawable/padded_circle_accent.xml new file mode 100644 index 0000000000..797c6bf007 --- /dev/null +++ b/app/src/main/res/drawable/padded_circle_accent.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/padded_circle_accent_select.xml b/app/src/main/res/drawable/padded_circle_accent_select.xml new file mode 100644 index 0000000000..0d384e658f --- /dev/null +++ b/app/src/main/res/drawable/padded_circle_accent_select.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/padded_circle_tintable.xml b/app/src/main/res/drawable/padded_circle_tintable.xml new file mode 100644 index 0000000000..ad91769c73 --- /dev/null +++ b/app/src/main/res/drawable/padded_circle_tintable.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/pill.xml b/app/src/main/res/drawable/pill.xml index 57cfd3a85c..f279f0f921 100644 --- a/app/src/main/res/drawable/pill.xml +++ b/app/src/main/res/drawable/pill.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/pn_option_background.xml b/app/src/main/res/drawable/pn_option_background.xml index b43e8de341..f84067c640 100644 --- a/app/src/main/res/drawable/pn_option_background.xml +++ b/app/src/main/res/drawable/pn_option_background.xml @@ -4,7 +4,7 @@ android:shape="rectangle" > + android:color="?colorPrimary" /> + android:color="?colorPrimary" /> diff --git a/app/src/main/res/drawable/preference_bottom.xml b/app/src/main/res/drawable/preference_bottom.xml new file mode 100644 index 0000000000..d751c7841b --- /dev/null +++ b/app/src/main/res/drawable/preference_bottom.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/preference_middle.xml b/app/src/main/res/drawable/preference_middle.xml new file mode 100644 index 0000000000..0f7229cb7f --- /dev/null +++ b/app/src/main/res/drawable/preference_middle.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/preference_single.xml b/app/src/main/res/drawable/preference_single.xml new file mode 100644 index 0000000000..da856fcd18 --- /dev/null +++ b/app/src/main/res/drawable/preference_single.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/preference_single_no_padding.xml b/app/src/main/res/drawable/preference_single_no_padding.xml new file mode 100644 index 0000000000..252ab0aea3 --- /dev/null +++ b/app/src/main/res/drawable/preference_single_no_padding.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/preference_top.xml b/app/src/main/res/drawable/preference_top.xml new file mode 100644 index 0000000000..a997713e23 --- /dev/null +++ b/app/src/main/res/drawable/preference_top.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/prominent_outline_button_medium_background.xml b/app/src/main/res/drawable/prominent_outline_button_medium_background.xml index f595e4f38b..ee3bec8f7f 100644 --- a/app/src/main/res/drawable/prominent_outline_button_medium_background.xml +++ b/app/src/main/res/drawable/prominent_outline_button_medium_background.xml @@ -7,5 +7,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml b/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml index f595e4f38b..5f0a51ec40 100644 --- a/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml +++ b/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml @@ -7,5 +7,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/quote_accent_line.xml b/app/src/main/res/drawable/quote_accent_line.xml new file mode 100644 index 0000000000..5693f4991b --- /dev/null +++ b/app/src/main/res/drawable/quote_accent_line.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radial_multi_select.xml b/app/src/main/res/drawable/radial_multi_select.xml new file mode 100644 index 0000000000..c05af4e763 --- /dev/null +++ b/app/src/main/res/drawable/radial_multi_select.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radial_select.xml b/app/src/main/res/drawable/radial_select.xml new file mode 100644 index 0000000000..a56519ed6e --- /dev/null +++ b/app/src/main/res/drawable/radial_select.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rationale_heading_background.xml b/app/src/main/res/drawable/rationale_heading_background.xml new file mode 100644 index 0000000000..7fb5b2f808 --- /dev/null +++ b/app/src/main/res/drawable/rationale_heading_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/reaction_pill_background.xml b/app/src/main/res/drawable/reaction_pill_background.xml index 02d56da9ba..136bd9e6b0 100644 --- a/app/src/main/res/drawable/reaction_pill_background.xml +++ b/app/src/main/res/drawable/reaction_pill_background.xml @@ -1,6 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/reaction_pill_background_selected.xml b/app/src/main/res/drawable/reaction_pill_background_selected.xml index cdc2488629..e969011b77 100644 --- a/app/src/main/res/drawable/reaction_pill_background_selected.xml +++ b/app/src/main/res/drawable/reaction_pill_background_selected.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/drawable/reaction_pill_dialog_background.xml b/app/src/main/res/drawable/reaction_pill_dialog_background.xml index 0b79d420dd..136bd9e6b0 100644 --- a/app/src/main/res/drawable/reaction_pill_dialog_background.xml +++ b/app/src/main/res/drawable/reaction_pill_dialog_background.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/search_bar_end.xml b/app/src/main/res/drawable/search_bar_end.xml index cf31e32384..3fb796d88c 100644 --- a/app/src/main/res/drawable/search_bar_end.xml +++ b/app/src/main/res/drawable/search_bar_end.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_bar_start.xml b/app/src/main/res/drawable/search_bar_start.xml index 8ee0f611ca..3abbe7ea96 100644 --- a/app/src/main/res/drawable/search_bar_start.xml +++ b/app/src/main/res/drawable/search_bar_start.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/session_edit_text_cursor.xml b/app/src/main/res/drawable/session_edit_text_cursor.xml index af3edcf4de..78aef55910 100644 --- a/app/src/main/res/drawable/session_edit_text_cursor.xml +++ b/app/src/main/res/drawable/session_edit_text_cursor.xml @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/session_id_text_view_background.xml b/app/src/main/res/drawable/session_id_text_view_background.xml index 3d6f49d7aa..d86bc87bcd 100644 --- a/app/src/main/res/drawable/session_id_text_view_background.xml +++ b/app/src/main/res/drawable/session_id_text_view_background.xml @@ -4,7 +4,7 @@ android:shape="rectangle" > diff --git a/app/src/main/res/drawable/setting_button_background.xml b/app/src/main/res/drawable/setting_button_background.xml index ee5f321c99..aaceb7ed54 100644 --- a/app/src/main/res/drawable/setting_button_background.xml +++ b/app/src/main/res/drawable/setting_button_background.xml @@ -1,9 +1,9 @@ + android:color="?colorCellRipple"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/unimportant_dialog_button_background.xml b/app/src/main/res/drawable/unimportant_dialog_button_background.xml index a1d03bb009..c517c1a135 100644 --- a/app/src/main/res/drawable/unimportant_dialog_button_background.xml +++ b/app/src/main/res/drawable/unimportant_dialog_button_background.xml @@ -3,9 +3,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable-notnight/prominent_filled_button_medium_background.xml b/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml similarity index 58% rename from app/src/main/res/drawable-notnight/prominent_filled_button_medium_background.xml rename to app/src/main/res/drawable/unimportant_dialog_text_button_background.xml index 6f6bd22a51..1eff84a69b 100644 --- a/app/src/main/res/drawable-notnight/prominent_filled_button_medium_background.xml +++ b/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml @@ -3,9 +3,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - + - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/view_lock_background.xml b/app/src/main/res/drawable/view_lock_background.xml index 9d3510cd34..35c3203487 100644 --- a/app/src/main/res/drawable/view_lock_background.xml +++ b/app/src/main/res/drawable/view_lock_background.xml @@ -3,6 +3,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/view_scroll_to_bottom_button_background.xml b/app/src/main/res/drawable/view_scroll_to_bottom_button_background.xml index 512b3861a7..600332f7d1 100644 --- a/app/src/main/res/drawable/view_scroll_to_bottom_button_background.xml +++ b/app/src/main/res/drawable/view_scroll_to_bottom_button_background.xml @@ -2,6 +2,6 @@ - - + + diff --git a/app/src/main/res/layout-sw400dp/activity_display_name.xml b/app/src/main/res/layout-sw400dp/activity_display_name.xml index fe00d541a2..50986e7dd4 100644 --- a/app/src/main/res/layout-sw400dp/activity_display_name.xml +++ b/app/src/main/res/layout-sw400dp/activity_display_name.xml @@ -17,7 +17,7 @@ android:layout_marginRight="@dimen/very_large_spacing" android:textSize="@dimen/very_large_font_size" android:textStyle="bold" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/activity_display_name_title_2" /> @@ -55,7 +55,7 @@ android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textSize="@dimen/very_small_font_size" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/activity_pn_mode_fast_mode_explanation" /> @@ -84,7 +84,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/medium_font_size" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:textStyle="bold" android:text="@string/activity_pn_mode_slow_mode" /> @@ -93,7 +93,7 @@ android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textSize="@dimen/very_small_font_size" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/activity_pn_mode_slow_mode_explanation" /> diff --git a/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml b/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml index b88592a68c..f801235b4f 100644 --- a/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml +++ b/app/src/main/res/layout-sw400dp/activity_recovery_phrase_restore.xml @@ -18,7 +18,7 @@ android:layout_marginRight="@dimen/very_large_spacing" android:textSize="@dimen/very_large_font_size" android:textStyle="bold" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/activity_restore_title" /> diff --git a/app/src/main/res/layout-sw400dp/fragment_recovery_phrase.xml b/app/src/main/res/layout-sw400dp/fragment_recovery_phrase.xml index ee4176b83f..9a51b14519 100644 --- a/app/src/main/res/layout-sw400dp/fragment_recovery_phrase.xml +++ b/app/src/main/res/layout-sw400dp/fragment_recovery_phrase.xml @@ -17,7 +17,7 @@ android:layout_marginRight="@dimen/very_large_spacing" android:textSize="@dimen/very_large_font_size" android:textStyle="bold" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/fragment_recovery_phrase_title" /> @@ -43,7 +42,7 @@ android:layout_width="260dp" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:textColor="@color/text" + android:textColor="?android:textColor" android:textSize="@dimen/very_small_font_size" android:lines="2" android:alpha="0.6" diff --git a/app/src/main/res/layout/activity_appearance_settings.xml b/app/src/main/res/layout/activity_appearance_settings.xml new file mode 100644 index 0000000000..fbafa632d5 --- /dev/null +++ b/app/src/main/res/layout/activity_appearance_settings.xml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_blocked_contacts.xml b/app/src/main/res/layout/activity_blocked_contacts.xml new file mode 100644 index 0000000000..69d0043009 --- /dev/null +++ b/app/src/main/res/layout/activity_blocked_contacts.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 5f8d4ec7c6..ae7aa3c689 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -1,5 +1,6 @@ + app:tint="?android:textColorPrimary" /> @@ -146,7 +151,7 @@ android:layout_centerHorizontal="true" android:layout_alignParentTop="true" android:background="@drawable/rounded_rectangle" - android:backgroundTint="@color/conversation_unread_count_indicator_background"> + android:backgroundTint="?conversation_unread_count_indicator_background"> @@ -220,7 +225,7 @@ android:alpha="0.6" android:gravity="center_horizontal" android:text="@string/message_requests_send_notice" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:textSize="@dimen/small_font_size" /> @@ -54,7 +54,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Muted" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:alpha="0.6" android:textSize="@dimen/very_small_font_size" android:maxLines="1" diff --git a/app/src/main/res/layout/activity_display_name.xml b/app/src/main/res/layout/activity_display_name.xml index 60a6580cef..57ebd0d7fc 100644 --- a/app/src/main/res/layout/activity_display_name.xml +++ b/app/src/main/res/layout/activity_display_name.xml @@ -17,7 +17,7 @@ android:layout_marginRight="@dimen/very_large_spacing" android:textSize="20sp" android:textStyle="bold" - android:textColor="@color/text" + android:textColor="?android:textColorPrimary" android:text="@string/activity_display_name_title_2" />