distinguished incoming and outgoing message with color

supported notification channel settings on Android 8.0
This commit is contained in:
Mariotaku Lee 2017-08-30 20:17:55 +08:00
parent b4224b099d
commit 20d014e370
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
12 changed files with 248 additions and 95 deletions

View File

@ -20,14 +20,19 @@
package org.mariotaku.twidere.adapter package org.mariotaku.twidere.adapter
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.support.v4.graphics.ColorUtils
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import org.apache.commons.lang3.time.DateUtils import org.apache.commons.lang3.time.DateUtils
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.library.objectcursor.ObjectCursor import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.PreviewStyle import org.mariotaku.twidere.annotation.PreviewStyle
@ -40,6 +45,7 @@ import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessage.MessageType import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.provider.TwidereDataStore.Messages import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.util.TwidereLinkify import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
@ -78,6 +84,9 @@ class MessagesConversationAdapter(
var listener: Listener? = null var listener: Listener? = null
var displaySenderProfile: Boolean = false var displaySenderProfile: Boolean = false
val bubbleColorOutgoing: ColorStateList? = ThemeUtils.getColorStateListFromAttribute(context, R.attr.messageBubbleColor)
val bubbleColorIncoming: ColorStateList? = context.getIncomingMessageColor()
override var loadMoreIndicatorPosition: Long override var loadMoreIndicatorPosition: Long
get() = super.loadMoreIndicatorPosition get() = super.loadMoreIndicatorPosition
set(value) { set(value) {
@ -213,6 +222,19 @@ class MessagesConversationAdapter(
private const val ITEM_TYPE_STICKER_MESSAGE = 2 private const val ITEM_TYPE_STICKER_MESSAGE = 2
private const val ITEM_TYPE_NOTICE_MESSAGE = 3 private const val ITEM_TYPE_NOTICE_MESSAGE = 3
private const val ITEM_LOAD_OLDER_INDICATOR = 4 private const val ITEM_LOAD_OLDER_INDICATOR = 4
private fun Context.getIncomingMessageColor(): ColorStateList {
val foregroundColor = ThemeUtils.getColorForeground(this)
val themeColor = Chameleon.getOverrideTheme(this, ChameleonUtils.getActivity(this)).colorAccent
val normalColor = ThemeUtils.getOptimalAccentColor(themeColor, foregroundColor)
val pressedColor = if (ColorUtils.calculateLuminance(normalColor) < 0.1) {
ColorUtils.compositeColors(0x20FFFFFF, normalColor)
} else {
ColorUtils.compositeColors(0x20000000, normalColor)
}
return ColorStateList(arrayOf(intArrayOf(android.R.attr.state_pressed), intArrayOf(0)),
intArrayOf(pressedColor, normalColor))
}
} }

View File

@ -41,3 +41,12 @@ fun UserKey.notificationChannelId(id: String): String {
fun UserKey.notificationChannelGroupId(): String { fun UserKey.notificationChannelGroupId(): String {
return Uri.encode(toString()) return Uri.encode(toString())
} }
fun NotificationChannelSpec.getName(context: Context): String {
return context.getString(nameRes)
}
fun NotificationChannelSpec.getDescription(context: Context): String? {
if (descriptionRes == 0) return null
return context.getString(descriptionRes)
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.fragment
import android.os.Bundle import android.os.Bundle
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.SharedPreferenceConstants.* import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
import org.mariotaku.twidere.preference.notification.AccountNotificationChannelsPreference
class AccountNotificationSettingsFragment : BaseAccountPreferenceFragment() { class AccountNotificationSettingsFragment : BaseAccountPreferenceFragment() {
@ -36,11 +37,13 @@ class AccountNotificationSettingsFragment : BaseAccountPreferenceFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
val preference = findPreference(KEY_NOTIFICATION_LIGHT_COLOR) val account = this.account
val account = account findPreference(KEY_NOTIFICATION_LIGHT_COLOR)?.let {
if (preference != null && account != null) { if (account != null) {
preference.setDefaultValue(account.color) it.setDefaultValue(account.color)
} }
} }
(findPreference("notification_channels") as? AccountNotificationChannelsPreference)?.account = account
}
} }

View File

@ -59,12 +59,14 @@ enum class NotificationChannelSpec(
* Such as new statuses posted by friends. * Such as new statuses posted by friends.
*/ */
contentUpdates("content_updates", R.string.notification_channel_name_content_updates, contentUpdates("content_updates", R.string.notification_channel_name_content_updates,
descriptionRes = R.string.notification_channel_descriptions_content_updates,
importance = NotificationManager.IMPORTANCE_DEFAULT, showBadge = true, grouped = true), importance = NotificationManager.IMPORTANCE_DEFAULT, showBadge = true, grouped = true),
/** /**
* For updates related to micro-blogging features. * For updates related to micro-blogging features.
* Such as new statuses posted by friends user subscribed to. * Such as new statuses posted by friends user subscribed to.
*/ */
contentSubscriptions("content_subscriptions", R.string.notification_channel_name_content_subscriptions, contentSubscriptions("content_subscriptions", R.string.notification_channel_name_content_subscriptions,
descriptionRes = R.string.notification_channel_descriptions_content_subscriptions,
importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true, grouped = true), importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true, grouped = true),
/** /**
* For interactions related to micro-blogging features. * For interactions related to micro-blogging features.

View File

@ -20,6 +20,7 @@
package org.mariotaku.twidere.preference package org.mariotaku.twidere.preference
import android.accounts.AccountManager import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
@ -93,7 +94,7 @@ abstract class AccountsListPreference(context: Context, attrs: AttributeSet? = n
switchPreference = context.getSharedPreferences(switchPreferenceName, Context.MODE_PRIVATE) switchPreference = context.getSharedPreferences(switchPreferenceName, Context.MODE_PRIVATE)
switchPreference.registerOnSharedPreferenceChangeListener(this) switchPreference.registerOnSharedPreferenceChangeListener(this)
title = account.user.name title = account.user.name
summary = String.format("@%s", account.user.screen_name) summary = "@${account.user.screen_name}"
widgetLayoutResource = R.layout.layout_preference_switch_indicator widgetLayoutResource = R.layout.layout_preference_switch_indicator
} }
@ -102,17 +103,17 @@ abstract class AccountsListPreference(context: Context, attrs: AttributeSet? = n
} }
@SuppressLint("RestrictedApi")
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
val iconView = holder.findViewById(android.R.id.icon) val iconView = holder.findViewById(android.R.id.icon)
if (iconView is PreferenceImageView) { if (iconView is PreferenceImageView) {
val imageView = iconView
val maxSize = context.resources.getDimensionPixelSize(R.dimen.element_size_normal) val maxSize = context.resources.getDimensionPixelSize(R.dimen.element_size_normal)
imageView.minimumWidth = maxSize iconView.minimumWidth = maxSize
imageView.minimumHeight = maxSize iconView.minimumHeight = maxSize
imageView.maxWidth = maxSize iconView.maxWidth = maxSize
imageView.maxHeight = maxSize iconView.maxHeight = maxSize
imageView.scaleType = ImageView.ScaleType.CENTER_CROP iconView.scaleType = ImageView.ScaleType.CENTER_CROP
} }
val titleView = holder.findViewById(android.R.id.title) val titleView = holder.findViewById(android.R.id.title)
if (titleView is TextView) { if (titleView is TextView) {

View File

@ -0,0 +1,66 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.preference.notification
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceManager
import android.util.AttributeSet
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.extension.model.getDescription
import org.mariotaku.twidere.extension.model.getName
import org.mariotaku.twidere.extension.model.notificationChannelId
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.preference.TintedPreferenceCategory
@TargetApi(Build.VERSION_CODES.O)
class AccountNotificationChannelsPreference(context: Context, attrs: AttributeSet? = null) : TintedPreferenceCategory(context, attrs) {
var account: AccountDetails? = null
override fun onAttachedToHierarchy(preferenceManager: PreferenceManager?) {
super.onAttachedToHierarchy(preferenceManager)
initItems()
}
private fun initItems() {
removeAll()
val specs = NotificationChannelSpec.values().filter { it.grouped }.sortedBy { it.getName(context) }
specs.forEach { spec ->
val preference = Preference(context)
preference.title = spec.getName(context)
preference.summary = spec.getDescription(context)
preference.setOnPreferenceClickListener lambda@ {
val account = this.account ?: return@lambda true
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
.putExtra(Settings.EXTRA_CHANNEL_ID, account.key.notificationChannelId(spec.id))
context.startActivity(intent)
return@lambda true
}
addPreference(preference)
}
}
}

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.util
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
@ -458,6 +459,15 @@ object ThemeUtils {
} }
} }
fun getColorStateListFromAttribute(context: Context, @AttrRes attr: Int, styleRes: Int = 0): ColorStateList? {
val a = context.obtainStyledAttributes(null, intArrayOf(attr), 0, styleRes)
try {
return a.getColorStateList(0)
} finally {
a.recycle()
}
}
fun getBooleanFromAttribute(context: Context, @AttrRes attr: Int, styleRes: Int = 0, def: Boolean = false): Boolean { fun getBooleanFromAttribute(context: Context, @AttrRes attr: Int, styleRes: Int = 0, def: Boolean = false): Boolean {
val a = context.obtainStyledAttributes(null, intArrayOf(attr), 0, styleRes) val a = context.obtainStyledAttributes(null, intArrayOf(attr), 0, styleRes)
try { try {

View File

@ -31,6 +31,7 @@ import org.mariotaku.twidere.adapter.MessagesConversationAdapter
import org.mariotaku.twidere.extension.model.applyTo import org.mariotaku.twidere.extension.model.applyTo
import org.mariotaku.twidere.model.ParcelableMessage import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.SpanItem import org.mariotaku.twidere.model.SpanItem
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.view.FixedTextView import org.mariotaku.twidere.view.FixedTextView
import org.mariotaku.twidere.view.ProfileImageView import org.mariotaku.twidere.view.ProfileImageView
@ -64,8 +65,15 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
override fun display(message: ParcelableMessage, showDate: Boolean) { override fun display(message: ParcelableMessage, showDate: Boolean) {
super.display(message, showDate) super.display(message, showDate)
messageBubble.bubbleColor = if (message.is_outgoing) {
adapter.bubbleColorOutgoing
} else {
adapter.bubbleColorIncoming
}
messageBubble.setOutgoing(message.is_outgoing) messageBubble.setOutgoing(message.is_outgoing)
text.setTextColor(ThemeUtils.getColorDependent(messageBubble.bubbleColor.defaultColor))
// Loop through text and spans to found non-space char count // Loop through text and spans to found non-space char count
val hideText = run { val hideText = run {

View File

@ -55,6 +55,7 @@
android:layout_toEndOf="@+id/profileImage" android:layout_toEndOf="@+id/profileImage"
android:layout_toRightOf="@+id/profileImage" android:layout_toRightOf="@+id/profileImage"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:minHeight="@dimen/profile_image_size_direct_message" android:minHeight="@dimen/profile_image_size_direct_message"
android:minWidth="@dimen/element_size_normal" android:minWidth="@dimen/element_size_normal"
app:bubbleColor="?messageBubbleColor" app:bubbleColor="?messageBubbleColor"

View File

@ -774,6 +774,8 @@
<string name="notification_channel_description_content_interactions">Interactions like mentions and retweets</string> <string name="notification_channel_description_content_interactions">Interactions like mentions and retweets</string>
<string name="notification_channel_description_content_messages">Important messages like DMs</string> <string name="notification_channel_description_content_messages">Important messages like DMs</string>
<string name="notification_channel_descriptions_content_subscriptions">User\'s tweet notifications</string>
<string name="notification_channel_descriptions_content_updates">Updates like new tweets in home timeline</string>
<string name="notification_channel_name_app_notices">App notices</string> <string name="notification_channel_name_app_notices">App notices</string>
<string name="notification_channel_name_background_progresses">Background operations</string> <string name="notification_channel_name_background_progresses">Background operations</string>
<string name="notification_channel_name_content_interactions">Interactions</string> <string name="notification_channel_name_content_interactions">Interactions</string>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidElementNotAllowed -->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/notifications">
<org.mariotaku.twidere.preference.notification.AccountNotificationChannelsPreference
android:key="notification_channels"/>
<org.mariotaku.twidere.preference.TintedPreferenceCategory
android:key="cat_other_settings"
android:title="@string/other_settings">
<org.mariotaku.twidere.preference.ColorPickerPreference
android:key="notification_light_color"
android:title="@string/notification_light_color"
app:defaultColor="@color/branding_color"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="notification_following_only"
android:summary="@string/following_only_summary"
android:title="@string/following_only"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="notification_mentions_only"
android:title="@string/mentions_only"/>
</org.mariotaku.twidere.preference.TintedPreferenceCategory>
</PreferenceScreen>