supported notification channels

fixed some memory leaks
This commit is contained in:
Mariotaku Lee 2017-08-25 19:59:43 +08:00
parent 5a095a9178
commit 1d81c8cdf8
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
14 changed files with 296 additions and 98 deletions

View File

@ -38,7 +38,6 @@ import android.os.Build
import android.os.Bundle
import android.support.annotation.StringRes
import android.support.v4.app.Fragment
import android.support.v4.app.NotificationCompat
import android.support.v4.view.GravityCompat
import android.support.v4.view.ViewCompat
import android.support.v4.view.ViewPager.OnPageChangeListener
@ -79,6 +78,7 @@ import org.mariotaku.twidere.annotation.NavbarStyle
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.model.notificationBuilder
import org.mariotaku.twidere.extension.onShow
import org.mariotaku.twidere.fragment.AccountsDashboardFragment
import org.mariotaku.twidere.fragment.BaseDialogFragment
@ -91,6 +91,7 @@ import org.mariotaku.twidere.model.SupportTabSpec
import org.mariotaku.twidere.model.Tab
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
@ -99,6 +100,7 @@ import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
import org.mariotaku.twidere.view.HomeDrawerLayout
import org.mariotaku.twidere.view.TabPagerIndicator
import java.lang.ref.WeakReference
class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, SupportFragmentCallback,
OnLongClickListener, DrawerLayout.DrawerListener {
@ -118,6 +120,18 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
private val readStateChangeListener = OnSharedPreferenceChangeListener { _, _ -> updateUnreadCount() }
private val controlBarShowHideHelper = ControlBarShowHideHelper(this)
override val controlBarHeight: Int
get() {
return mainTabs.height - mainTabs.stripHeight
}
override val currentVisibleFragment: Fragment?
get() {
val currentItem = mainPager.currentItem
if (currentItem < 0 || currentItem >= pagerAdapter.count) return null
return pagerAdapter.instantiateItem(mainPager, currentItem)
}
private val homeDrawerToggleDelegate = object : ActionBarDrawerToggle.Delegate {
override fun setActionBarUpIndicator(upDrawable: Drawable, @StringRes contentDescRes: Int) {
drawerToggleButton.setImageDrawable(upDrawable)
@ -146,34 +160,17 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
}
override val controlBarHeight: Int
get() {
return mainTabs.height - mainTabs.stripHeight
private val keyboardShortcutRecipient: Fragment?
get() = when {
homeMenu.isDrawerOpen(GravityCompat.START) -> leftDrawerFragment
homeMenu.isDrawerOpen(GravityCompat.END) -> null
else -> currentVisibleFragment
}
fun closeAccountsDrawer() {
if (homeMenu == null) return
homeMenu.closeDrawers()
}
private val activatedAccountKeys: Array<UserKey>
get() = DataStoreUtils.getActivatedAccountKeys(this)
override val currentVisibleFragment: Fragment?
get() {
val currentItem = mainPager.currentItem
if (currentItem < 0 || currentItem >= pagerAdapter.count) return null
return pagerAdapter.instantiateItem(mainPager, currentItem)
}
override fun triggerRefresh(position: Int): Boolean {
val f = pagerAdapter.instantiateItem(mainPager, position)
if (f.activity == null || f.isDetached) return false
if (f !is RefreshScrollTopInterface) return false
return f.triggerRefresh()
}
val leftDrawerFragment: Fragment?
private val leftDrawerFragment: Fragment?
get() = supportFragmentManager.findFragmentById(R.id.leftDrawer)
private val isDrawerOpen: Boolean
@ -220,7 +217,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
ViewCompat.setOnApplyWindowInsetsListener(homeContent, this)
homeMenu.fitsSystemWindows = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ||
preferences[navbarStyleKey] != NavbarStyle.TRANSPARENT
if (!homeMenu.fitsSystemWindows) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || !ViewCompat.getFitsSystemWindows(homeMenu)) {
ViewCompat.setOnApplyWindowInsetsListener(homeMenu, null)
}
@ -544,6 +541,12 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
return super.onKeyUp(keyCode, event)
}
override fun triggerRefresh(position: Int): Boolean {
val f = pagerAdapter.instantiateItem(mainPager, position)
if (f.activity == null || f.isDetached) return false
if (f !is RefreshScrollTopInterface) return false
return f.triggerRefresh()
}
fun notifyAccountsChanged() {
}
@ -553,11 +556,6 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
updateUnreadCount()
}
fun openSearchView(account: AccountDetails?) {
selectedAccountToSearch = account
onSearchRequested()
}
fun updateUnreadCount() {
if (mainTabs == null || updateUnreadCountTask != null && updateUnreadCountTask!!.status == AsyncTask.Status.RUNNING)
return
@ -580,10 +578,10 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
if (mainTabs.columns > 1) {
val lp = actionsButton.layoutParams
val total: Float
if (lp is MarginLayoutParams) {
total = (lp.bottomMargin + actionsButton.height).toFloat()
total = if (lp is MarginLayoutParams) {
(lp.bottomMargin + actionsButton.height).toFloat()
} else {
total = actionsButton.height.toFloat()
actionsButton.height.toFloat()
}
return 1 - actionsButton.translationY / total
}
@ -626,16 +624,15 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
return homeDrawerToggleDelegate
}
private val keyboardShortcutRecipient: Fragment?
get() {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {
return leftDrawerFragment
} else if (homeMenu.isDrawerOpen(GravityCompat.END)) {
return null
} else {
return currentVisibleFragment
}
}
fun closeAccountsDrawer() {
if (homeMenu == null) return
homeMenu.closeDrawers()
}
private fun openSearchView(account: AccountDetails?) {
selectedAccountToSearch = account
onSearchRequested()
}
private fun handleFragmentKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler,
keyCode: Int, repeatCount: Int,
@ -678,11 +675,10 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
if (Intent.ACTION_SEARCH == action) {
val query = intent.getStringExtra(SearchManager.QUERY)
val appSearchData = intent.getBundleExtra(SearchManager.APP_DATA)
val accountKey: UserKey?
if (appSearchData != null && appSearchData.containsKey(EXTRA_ACCOUNT_KEY)) {
accountKey = appSearchData.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
val accountKey = if (appSearchData != null && appSearchData.containsKey(EXTRA_ACCOUNT_KEY)) {
appSearchData.getParcelable(EXTRA_ACCOUNT_KEY)
} else {
accountKey = Utils.getDefaultAccountKey(this)
Utils.getDefaultAccountKey(this)
}
IntentUtils.openSearch(this, accountKey, query)
return -1
@ -847,7 +843,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
val intent = Intent(this, UsageStatisticsActivity::class.java)
val contentIntent = PendingIntent.getActivity(this, 0, intent, 0)
val builder = NotificationCompat.Builder(this)
val builder = NotificationChannelSpec.appNotices.notificationBuilder(this)
builder.setAutoCancel(true)
builder.setSmallIcon(R.drawable.ic_stat_info)
builder.setTicker(getString(R.string.usage_statistics))
@ -889,7 +885,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
fun hasMultiColumns(): Boolean {
private fun hasMultiColumns(): Boolean {
if (!DeviceUtils.isDeviceTablet(this) || !DeviceUtils.isScreenTablet(this)) return false
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
return preferences.getBoolean("multi_column_tabs_landscape", resources.getBoolean(R.bool.default_multi_column_tabs_land))
@ -907,17 +903,20 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
private class UpdateUnreadCountTask(
private val context: Context,
context: Context,
private val preferences: SharedPreferences,
private val readStateManager: ReadStateManager,
private val indicator: TabPagerIndicator,
indicator: TabPagerIndicator,
private val tabs: Array<SupportTabSpec>
) : AsyncTask<Any, UpdateUnreadCountTask.TabBadge, SparseIntArray>() {
private val activatedKeys = DataStoreUtils.getActivatedAccountKeys(context)
private val contextRef = WeakReference(context)
private val indicatorRef = WeakReference(indicator)
override fun doInBackground(vararg params: Any): SparseIntArray {
val result = SparseIntArray()
val context = contextRef.get() ?: return result
tabs.forEachIndexed { i, spec ->
if (spec.type == null) {
publishProgress(TabBadge(i, -1))
@ -969,6 +968,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
override fun onPostExecute(result: SparseIntArray) {
val indicator = indicatorRef.get() ?: return
indicator.clearBadge()
for (i in 0 until result.size()) {
indicator.setBadge(result.keyAt(i), result.valueAt(i))
@ -976,6 +976,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
override fun onProgressUpdate(vararg values: TabBadge) {
val indicator = indicatorRef.get() ?: return
for (value in values) {
indicator.setBadge(value.index, value.count)
}

View File

@ -35,9 +35,6 @@ import android.view.View
import android.widget.Toast
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.resource.drawable.GlideDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import kotlinx.android.synthetic.main.activity_main.*
import nl.komponents.kovenant.Promise
import org.mariotaku.chameleon.Chameleon
@ -52,7 +49,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.activity.iface.IBaseActivity
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
import org.mariotaku.twidere.constant.lastLaunchPresentationTimeKey
import org.mariotaku.twidere.constant.lastLaunchTimeKey
import org.mariotaku.twidere.constant.promotionsEnabledKey
import org.mariotaku.twidere.constant.themeColorKey
import org.mariotaku.twidere.constant.themeKey
@ -148,14 +145,14 @@ open class MainActivity : ChameleonActivity(), IBaseActivity<MainActivity> {
}
private fun showPresentationOrLaunch() {
val lastLaunchPresentationTime = preferences[lastLaunchPresentationTimeKey]
val lastLaunchTime = preferences[lastLaunchTimeKey]
val maximumDuration = if (BuildConfig.DEBUG) {
TimeUnit.SECONDS.toMillis(30)
} else {
TimeUnit.HOURS.toMillis(6)
}
// Show again at least 6 hours later (30 secs for debug builds)
if (lastLaunchPresentationTime >= 0 && System.currentTimeMillis() - lastLaunchPresentationTime < maximumDuration) {
if (lastLaunchTime >= 0 && System.currentTimeMillis() - lastLaunchTime < maximumDuration) {
launchDirectly()
return
}
@ -212,20 +209,6 @@ open class MainActivity : ChameleonActivity(), IBaseActivity<MainActivity> {
skipPresentation.visibility = View.VISIBLE
controlOverlay.tag = presentation
Glide.with(this).load(presentation.images.first().url)
.listener(object : RequestListener<String, GlideDrawable> {
override fun onException(e: Exception?, model: String?,
target: Target<GlideDrawable>?, isFirstResource: Boolean): Boolean {
return false
}
override fun onResourceReady(resource: GlideDrawable?, model: String?,
target: Target<GlideDrawable>?, isFromMemoryCache: Boolean,
isFirstResource: Boolean): Boolean {
preferences[lastLaunchPresentationTimeKey] = System.currentTimeMillis()
return false
}
})
.priority(Priority.HIGH)
.into(presentationView)
}
@ -245,6 +228,7 @@ open class MainActivity : ChameleonActivity(), IBaseActivity<MainActivity> {
}
private fun performLaunch() {
preferences[lastLaunchTimeKey] = System.currentTimeMillis()
val am = AccountManager.get(this)
if (!DeviceUtils.checkCompatibility()) {
startActivity(Intent(this, IncompatibleAlertActivity::class.java))

View File

@ -65,6 +65,8 @@ import org.mariotaku.twidere.util.kovenant.stopKovenant
import org.mariotaku.twidere.util.media.MediaPreloader
import org.mariotaku.twidere.util.media.ThumborWrapper
import org.mariotaku.twidere.util.net.TwidereDns
import org.mariotaku.twidere.util.notification.ContentNotificationManager
import org.mariotaku.twidere.util.notification.NotificationChannelsManager
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.refresh.AutoRefreshController
import org.mariotaku.twidere.util.sync.DataSyncProvider
@ -132,6 +134,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
}
super.onCreate()
EmojioneTranslator.init(this)
NotificationChannelsManager.createChannels(this)
applyLanguageSettings()
startKovenant()
initializeAsyncTask()

View File

@ -85,7 +85,7 @@ val homeRefreshDirectMessagesKey = KBooleanKey(KEY_HOME_REFRESH_DIRECT_MESSAGES,
val homeRefreshSavedSearchesKey = KBooleanKey(KEY_HOME_REFRESH_SAVED_SEARCHES, true)
val composeStatusVisibilityKey = KNullableStringKey("compose_status_visibility", null)
val navbarStyleKey = KStringKey(KEY_NAVBAR_STYLE, NavbarStyle.DEFAULT)
val lastLaunchPresentationTimeKey = KLongKey("last_launch_presentation_time", -1)
val lastLaunchTimeKey = KLongKey("last_launch_time", -1)
val promotionsEnabledKey = KBooleanKey("promotions_enabled", false)
object cacheSizeLimitKey : KSimpleKey<Int>(KEY_CACHE_SIZE_LIMIT, 300) {

View File

@ -0,0 +1,31 @@
/*
* 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.extension.model
import android.content.Context
import android.support.v4.app.NotificationCompat
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
/**
* Created by mariotaku on 2017/8/25.
*/
fun NotificationChannelSpec.notificationBuilder(context: Context): NotificationCompat.Builder {
return NotificationCompat.Builder(context, id)
}

View File

@ -0,0 +1,89 @@
/*
* 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.model.notification
import android.app.NotificationManager
import android.support.annotation.StringRes
import org.mariotaku.twidere.R
/**
* Created by mariotaku on 2017/8/25.
*/
enum class NotificationChannelSpec(
val id: String,
@StringRes val nameRes: Int,
@StringRes val descriptionRes: Int = 0,
val importance: Int,
val showBadge: Boolean = false) {
/**
* For notifications send by app itself.
* Such as "what's new"
*/
appNotices("app_notices", R.string.notification_channel_name_app_notices,
importance = NotificationManager.IMPORTANCE_LOW, showBadge = true),
/**
* For notifications indicate that some lengthy operations are performing in the background.
* Such as sending attachment process.
*/
backgroundProgresses("background_progresses", R.string.notification_channel_name_background_progresses,
importance = NotificationManager.IMPORTANCE_MIN),
/**
* For ongoing notifications indicating service statuses.
* Such as notification showing streaming service running
*/
serviceStatuses("service_statuses", R.string.notification_channel_name_service_statuses,
importance = NotificationManager.IMPORTANCE_MIN),
/**
* For import notifications related to micro-blogging features.
* Such as failure to update status.
*/
contentNotices("content_notices", R.string.notification_channel_name_content_notices,
importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true),
/**
* For updates related to micro-blogging features.
* Such as new statuses posted by friends.
*/
contentUpdates("content_updates", R.string.notification_channel_name_content_updates,
importance = NotificationManager.IMPORTANCE_DEFAULT, showBadge = true),
/**
* For updates related to micro-blogging features.
* Such as new statuses posted by friends user subscribed to.
*/
contentSubscriptions("content_subscriptions", R.string.notification_channel_name_content_subscriptions,
importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true),
/**
* For interactions related to micro-blogging features.
* Such as replies and likes.
*/
contentInteractions("content_interactions", R.string.notification_channel_name_content_interactions,
descriptionRes = R.string.notification_channel_description_content_interactions,
importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true),
/**
* For messages related to micro-blogging features.
* Such as direct messages.
*/
contentMessages("content_messages", R.string.notification_channel_name_content_messages,
descriptionRes = R.string.notification_channel_description_content_messages,
importance = NotificationManager.IMPORTANCE_HIGH, showBadge = true)
}

View File

@ -54,6 +54,7 @@ import org.mariotaku.twidere.util.SQLiteDatabaseWrapper.LazyLoadCallback
import org.mariotaku.twidere.util.dagger.GeneralComponent
import org.mariotaku.twidere.util.database.CachedUsersQueryBuilder
import org.mariotaku.twidere.util.database.SuggestionsCursorCreator
import org.mariotaku.twidere.util.notification.ContentNotificationManager
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import javax.inject.Inject

View File

@ -26,6 +26,7 @@ import com.twitter.Extractor
import com.twitter.Validator
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponent
import org.mariotaku.twidere.util.notification.ContentNotificationManager
import javax.inject.Inject
abstract class BaseService : Service() {

View File

@ -23,16 +23,13 @@ import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Service
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.provider.BaseColumns
import android.support.annotation.UiThread
import android.support.annotation.WorkerThread
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.Builder
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
@ -41,7 +38,6 @@ import nl.komponents.kovenant.ui.successUi
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.ManualTaskStarter
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.configure
import org.mariotaku.ktextension.getNullableTypedArrayExtra
import org.mariotaku.ktextension.toLongOr
import org.mariotaku.ktextension.useCursor
@ -58,9 +54,12 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.constant.refreshAfterTweetKey
import org.mariotaku.twidere.extension.getErrorMessage
import org.mariotaku.twidere.extension.model.notificationBuilder
import org.mariotaku.twidere.extension.withAppendedPath
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtras
import org.mariotaku.twidere.model.draft.StatusObjectActionExtras
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.model.schedule.ScheduleInfo
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils
@ -183,7 +182,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
private fun sendMessage(message: ParcelableNewMessage) {
val title = getString(R.string.sending_direct_message)
val builder = Builder(this)
val builder = NotificationChannelSpec.backgroundProgresses.notificationBuilder(this)
builder.setSmallIcon(R.drawable.ic_stat_send)
builder.setProgress(100, 0, true)
builder.setTicker(title)
@ -239,7 +238,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
private fun updateStatuses(statuses: Array<ParcelableStatusUpdate>, scheduleInfo: ScheduleInfo? = null) {
val context = this
val builder = Builder(context)
val builder = NotificationChannelSpec.backgroundProgresses.notificationBuilder(context)
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, null))
for (item in statuses) {
@ -321,9 +320,7 @@ class LengthyOperationsService : BaseIntentService("lengthy_operations") {
invokeAfterExecute(task, result)
if (!result.succeed) {
contentResolver.insert(Drafts.CONTENT_URI_NOTIFICATIONS, configure(ContentValues()) {
put(BaseColumns._ID, result.draftId)
})
contentResolver.insert(Drafts.CONTENT_URI_NOTIFICATIONS.withAppendedPath(result.draftId.toString()), null)
}
}
if (preferences[refreshAfterTweetKey]) {

View File

@ -36,6 +36,7 @@ import org.mariotaku.twidere.extension.model.api.key
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
import org.mariotaku.twidere.extension.model.api.toParcelable
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.*
@ -172,7 +173,7 @@ class StreamingService : BaseService() {
val contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val contentTitle = getString(R.string.app_name)
val contentText = getString(R.string.timeline_streaming_running)
val builder = NotificationCompat.Builder(this)
val builder = NotificationChannelSpec.serviceStatuses.notificationBuilder(this)
builder.setOngoing(true)
builder.setSmallIcon(R.drawable.ic_stat_streaming)
builder.setContentTitle(contentTitle)

View File

@ -58,6 +58,7 @@ import org.mariotaku.twidere.util.media.MediaPreloader
import org.mariotaku.twidere.util.media.ThumborWrapper
import org.mariotaku.twidere.util.media.TwidereMediaDownloader
import org.mariotaku.twidere.util.net.TwidereDns
import org.mariotaku.twidere.util.notification.ContentNotificationManager
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
import org.mariotaku.twidere.util.refresh.AutoRefreshController
import org.mariotaku.twidere.util.refresh.JobSchedulerAutoRefreshController

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util
package org.mariotaku.twidere.util.notification
import android.annotation.SuppressLint
import android.app.PendingIntent
@ -48,21 +48,22 @@ import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.extension.model.api.formattedTextWithIndices
import org.mariotaku.twidere.extension.model.getSummaryText
import org.mariotaku.twidere.extension.model.getTitle
import org.mariotaku.twidere.extension.model.notificationBuilder
import org.mariotaku.twidere.extension.model.notificationDisabled
import org.mariotaku.twidere.extension.rawQuery
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.receiver.NotificationReceiver
import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.database.FilterQueryBuilder
import org.oshkimaadziig.george.androidutils.SpanFormatter
import java.io.IOException
/**
* Created by mariotaku on 2017/2/16.
*/
class ContentNotificationManager(
val context: Context,
val activityTracker: ActivityTracker,
@ -134,7 +135,7 @@ class ContentNotificationManager(
}
// Setup notification
val builder = NotificationCompat.Builder(context)
val builder = NotificationChannelSpec.contentUpdates.notificationBuilder(context)
builder.setAutoCancel(true)
builder.setSmallIcon(R.drawable.ic_stat_twitter)
builder.setTicker(notificationTitle)
@ -175,7 +176,7 @@ class ContentNotificationManager(
@SuppressLint("Recycle")
val c = cr.query(Activities.AboutMe.CONTENT_URI, Activities.COLUMNS, where, whereArgs,
OrderBy(Activities.TIMESTAMP, false).sql) ?: return
val builder = NotificationCompat.Builder(context)
val builder = NotificationChannelSpec.contentInteractions.notificationBuilder(context)
val pebbleNotificationStringBuilder = StringBuilder()
try {
val count = c.count
@ -277,18 +278,18 @@ class ContentNotificationManager(
var messageSum: Int = 0
var newLastReadTimestamp = -1L
cur.forEachRow { cur, _ ->
val unreadCount = cur.getInt(indices[Conversations.UNREAD_COUNT])
cur.forEachRow { c, _ ->
val unreadCount = c.getInt(indices[Conversations.UNREAD_COUNT])
if (unreadCount <= 0) return@forEachRow false
if (newLastReadTimestamp != -1L) {
newLastReadTimestamp = cur.getLong(indices[Conversations.LAST_READ_TIMESTAMP])
newLastReadTimestamp = c.getLong(indices[Conversations.LAST_READ_TIMESTAMP])
}
messageSum += unreadCount
return@forEachRow true
}
if (messageSum == 0) return
val builder = NotificationCompat.Builder(context)
val builder = NotificationChannelSpec.contentMessages.notificationBuilder(context)
applyNotificationPreferences(builder, pref, pref.directMessagesNotificationType)
builder.setSmallIcon(R.drawable.ic_stat_message)
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
@ -305,8 +306,8 @@ class ContentNotificationManager(
builder.setDeleteIntent(getMarkReadDeleteIntent(context, NotificationType.DIRECT_MESSAGES,
accountKey, newLastReadTimestamp, false))
val remaining = cur.forEachRow(5) { cur, pos ->
val conversation = indices.newObject(cur)
val remaining = cur.forEachRow(5) { c, pos ->
val conversation = indices.newObject(c)
if (conversation.notificationDisabled) return@forEachRow false
val title = conversation.getTitle(context, userColorNameManager, nameFirst)
val summary = conversation.getSummaryText(context, userColorNameManager, nameFirst)
@ -336,7 +337,7 @@ class ContentNotificationManager(
val userDisplayName = userColorNameManager.getDisplayName(status.user,
preferences[nameFirstKey])
val statusUri = LinkCreator.getTwidereStatusLink(accountKey, status.id)
val builder = NotificationCompat.Builder(context)
val builder = NotificationChannelSpec.contentSubscriptions.notificationBuilder(context)
builder.color = userColorNameManager.getUserColor(userKey)
builder.setAutoCancel(true)
builder.setWhen(status.createdAt?.time ?: 0)
@ -381,7 +382,7 @@ class ContentNotificationManager(
uriBuilder.scheme(SCHEME_TWIDERE)
uriBuilder.authority(AUTHORITY_DRAFTS)
intent.data = uriBuilder.build()
val nb = NotificationCompat.Builder(context)
val nb = NotificationChannelSpec.contentNotices.notificationBuilder(context)
nb.setTicker(message)
nb.setContentTitle(title)
nb.setContentText(item.text)
@ -401,7 +402,6 @@ class ContentNotificationManager(
PendingIntent.getService(context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
nb.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT))
nb.setGroup("drafts")
notificationManager.notify(draftUri.toString(), NOTIFICATION_ID_DRAFTS, nb.build())
return draftId
}

View File

@ -0,0 +1,79 @@
/*
* 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.util.notification
import android.annotation.TargetApi
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.notification.NotificationChannelSpec
import org.mariotaku.twidere.util.dagger.DependencyHolder
/**
* Created by mariotaku on 2017/8/25.
*/
object NotificationChannelsManager {
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
NotificationChannelCreatorImpl.createChannels(context)
}
fun createAccountGroup(context: Context, account: AccountDetails) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
NotificationChannelCreatorImpl.createAccountGroup(context, account)
}
@TargetApi(Build.VERSION_CODES.O)
private object NotificationChannelCreatorImpl {
fun createChannels(context: Context) {
val nm = context.getSystemService(NotificationManager::class.java)
val values = NotificationChannelSpec.values()
nm.notificationChannels.filterNot { channel ->
values.any { channel.id == it.id }
}.forEach {
nm.deleteNotificationChannel(it.id)
}
for (spec in values) {
val channel = NotificationChannel(spec.id, context.getString(spec.nameRes), spec.importance)
if (spec.descriptionRes != 0) {
channel.description = context.getString(spec.descriptionRes)
}
channel.setShowBadge(spec.showBadge)
nm.createNotificationChannel(channel)
}
}
fun createAccountGroup(context: Context, account: AccountDetails) {
val nm = context.getSystemService(NotificationManager::class.java)
val holder = DependencyHolder.get(context)
val pref = holder.preferences
val ucnm = holder.userColorNameManager
val group = NotificationChannelGroup(account.key.toString(),
ucnm.getDisplayName(account.user, pref[nameFirstKey]))
nm.createNotificationChannelGroup(group)
}
}
}

View File

@ -44,6 +44,7 @@
<!-- [verb] Action for deleting a file or a twitter object like tweet-->
<string name="action_delete">Delete</string>
<string name="action_delete_messages">Delete messages</string>
<string name="action_disable_promotions">Disable promotions</string>
<string name="action_dont_restart">Don\'t restart</string>
<string name="action_dont_terminate">Don\'t quit</string>
<!-- [verb] Edit image/settings etc. -->
@ -769,6 +770,16 @@
<string name="none">None</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_name_app_notices">App notices</string>
<string name="notification_channel_name_background_progresses">Background operations</string>
<string name="notification_channel_name_content_interactions">Content interactions</string>
<string name="notification_channel_name_content_messages">Content messages</string>
<string name="notification_channel_name_content_notices">Content notices</string>
<string name="notification_channel_name_content_subscriptions">Content subscriptions</string>
<string name="notification_channel_name_content_updates">Content updates</string>
<string name="notification_channel_name_service_statuses">Service statuses</string>
<string name="notification_direct_message"><xliff:g id="user">%s</xliff:g> sent you a direct message.</string>
<string name="notification_direct_message_multiple_messages"><xliff:g id="user">%1$s</xliff:g> sent you <xliff:g id="messages_count">%2$d</xliff:g> direct messages.</string>
<string name="notification_direct_message_multiple_users"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="users_count">%2$d</xliff:g> others sent you <xliff:g id="messages_count">%3$d</xliff:g> direct messages.</string>
@ -1329,5 +1340,4 @@
<string name="users_blocked">Blocked these users.</string>
<string name="users_lists_with_name"><xliff:g id="name">%s</xliff:g>\'s lists</string>
<string name="users_statuses">User\'s tweets</string>
<string name="action_disable_promotions">Disable promotions</string>
</resources>