New app theming (#913)

* feat: start new app theming feature

* feat: add some theming colours

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

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

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

* feat: global search colours for light and dark ocean

* feat: more styling

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

* feat: add dynamic accent color

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

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

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

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

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

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

* fix: input bar bug fix and preference activity themes

* refactor: new settings menu options

* fix: crash for PNModeActivity.kt

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

* feat: add new appearance settings activity

* refactor: title and VM changes

* fix: correct override

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

* fix: compile errors and missing themes from emoji features

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

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

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

* refactor: add bottom margin

* feat: explore custom preference category

* feat: add the customized session theme for CorrectedPreferenceFragment

* feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment

* refactor: change drawable style and remove explicit dividers

* refactor: remove divider in CorrectedPreferenceFragment

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

* feat: add preference divider elements for settings menu

* refactor: settings menu redesigns

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

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

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

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

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

* feat: add blocked contacts and notif recipient listeners

* feat: add recipient db reader

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

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

* feat: add settings menu vectors

* fix: preview compile error

* refactor: migrating settings menu to new designs

* feat: help menu

* refactor: simplify link opening

* refactor: remove space

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

* refactor: fixing dark and light modes with dialogs

* refactor: popup dialogs use proper themes now

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

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

* refactor: transparency, dialog fixes, notification fix

* refactor: attrs and styles for buttons

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

* fix: fix the trash

* refactor: remove the appearance

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

* refactor: use colors instead of style references

* refactor: theming changes to match designs and feedback

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

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

* refactor: increase setting button height

* Update clear all data dialog

* Update seed dialog

* refactor: more qa feedback changes

* feat: add new TLs and fa-rIR TLs

* Update notification content dialog

* Fix message requests clear all button text color

* feat: re-add screenshot observer

* refactor: make send tint accent color

* feat: add unread background differences

* fix: change unread count indicator

* build: upgrade build numbers

* Fix message requests popupmenu background color

* fix: crash from attr reference in color attribute

* build: upgrade build number

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

* fix: tertiary text for keyboard page search view

* fix: emoji overflow colour differences

* fix: reaction pill dialog background is now correct colour

* Add style to reactions tab layout

* fix: appearance activity reverting primary color at correct time

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

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

* fix: crash in all media containing documents

* fix: reaction dialog heading fixes

* Add style to reactions tab layout

* fix: remove gradient backgrounds

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

* fix: ocean dark unread/read colours

* build; update build number

* build: update build number

Co-authored-by: charles <charles@oxen.io>
This commit is contained in:
Harris
2022-10-12 17:05:55 +11:00
committed by GitHub
parent 92075aed32
commit 7a773016da
378 changed files with 4305 additions and 3062 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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