diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/HomeActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/HomeActivity.kt index a3782b4e6..e81f415de 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/HomeActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/HomeActivity.kt @@ -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 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(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 ) : AsyncTask() { 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) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MainActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MainActivity.kt index 1bea17543..3d1ec8ea7 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MainActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MainActivity.kt @@ -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 { } 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 { skipPresentation.visibility = View.VISIBLE controlOverlay.tag = presentation Glide.with(this).load(presentation.images.first().url) - .listener(object : RequestListener { - override fun onException(e: Exception?, model: String?, - target: Target?, isFirstResource: Boolean): Boolean { - return false - } - - override fun onResourceReady(resource: GlideDrawable?, model: String?, - target: Target?, 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 { } private fun performLaunch() { + preferences[lastLaunchTimeKey] = System.currentTimeMillis() val am = AccountManager.get(this) if (!DeviceUtils.checkCompatibility()) { startActivity(Intent(this, IncompatibleAlertActivity::class.java)) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt index 397e9df36..630b36efe 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt @@ -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() diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt index 011164fd8..3d796eb12 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt @@ -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(KEY_CACHE_SIZE_LIMIT, 300) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/NotificationChannelSpecsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/NotificationChannelSpecsExtensions.kt new file mode 100644 index 000000000..6318d4d25 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/NotificationChannelSpecsExtensions.kt @@ -0,0 +1,31 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.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) +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/notification/NotificationChannelSpec.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/notification/NotificationChannelSpec.kt new file mode 100644 index 000000000..ceaae31ba --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/notification/NotificationChannelSpec.kt @@ -0,0 +1,89 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.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) + +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt index 61e20b33c..7a0eda3fb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt @@ -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 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt index 76a4df1b2..a9d0a16df 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt @@ -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() { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/LengthyOperationsService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/LengthyOperationsService.kt index bb2e64d3a..d14a3fd64 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/LengthyOperationsService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/LengthyOperationsService.kt @@ -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, 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]) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt index 56a5c6a0a..5edecadef 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt @@ -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) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt index 2d73268e0..7def82611 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt @@ -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 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt similarity index 95% rename from twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt rename to twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt index a1b4d1ca5..2ca8f4ce2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -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 } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/NotificationChannelsManager.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/NotificationChannelsManager.kt new file mode 100644 index 000000000..e059a4d85 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/NotificationChannelsManager.kt @@ -0,0 +1,79 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.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) + } + } +} \ No newline at end of file diff --git a/twidere/src/main/res/values/strings.xml b/twidere/src/main/res/values/strings.xml index cd1008d46..1a839df98 100644 --- a/twidere/src/main/res/values/strings.xml +++ b/twidere/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ Delete Delete messages + Disable promotions Don\'t restart Don\'t quit @@ -769,6 +770,16 @@ None + Interactions like mentions and retweets + Important messages like DMs + App notices + Background operations + Content interactions + Content messages + Content notices + Content subscriptions + Content updates + Service statuses %s sent you a direct message. %1$s sent you %2$d direct messages. %1$s and %2$d others sent you %3$d direct messages. @@ -1329,5 +1340,4 @@ Blocked these users. %s\'s lists User\'s tweets - Disable promotions