1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-02 17:56:56 +01:00

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

168
gradlew.bat vendored
View File

@ -1,84 +1,84 @@
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto init
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto init
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windows variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
set CMD_LINE_ARGS= set CMD_LINE_ARGS=
set _SKIP=2 set _SKIP=2
:win9xME_args_slurp :win9xME_args_slurp
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

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

@ -40,4 +40,13 @@ 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>