implemented delete single dm

This commit is contained in:
Mariotaku Lee 2017-02-16 14:50:25 +08:00
parent 20e5e70564
commit eee0227074
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
21 changed files with 665 additions and 387 deletions

View File

@ -17,3 +17,4 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048M org.gradle.jvmargs=-Xmx2048M
kotlin.incremental=true

View File

@ -54,6 +54,9 @@ public interface PrivateDirectMessagesResources extends PrivateResources {
@POST("/dm/new.json") @POST("/dm/new.json")
DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException; DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException;
@POST("/dm/destroy.json")
ResponseCode destroyDm(@Param("dm_id") String id) throws MicroBlogException;
@GET("/dm/user_inbox.json") @GET("/dm/user_inbox.json")
UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException; UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException;

View File

@ -62,7 +62,10 @@ import org.apache.commons.lang3.ObjectUtils
import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.* import org.mariotaku.ktextension.checkAnySelfPermissionsGranted
import org.mariotaku.ktextension.getTypedArray
import org.mariotaku.ktextension.setItemChecked
import org.mariotaku.ktextension.toTypedArray
import org.mariotaku.pickncrop.library.MediaPickerActivity import org.mariotaku.pickncrop.library.MediaPickerActivity
import org.mariotaku.twidere.Constants.* import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
@ -651,25 +654,32 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
} }
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo) {
if (v === attachedMediaPreview) { if (menuInfo !is ExtendedRecyclerView.ContextMenuInfo) return
menu.setHeaderTitle(R.string.edit_media) when (menuInfo.recyclerViewId) {
supportMenuInflater.inflate(R.menu.menu_attached_media_edit, menu) R.id.attachedMediaPreview -> {
menu.setHeaderTitle(R.string.edit_media)
supportMenuInflater.inflate(R.menu.menu_attached_media_edit, menu)
}
} }
} }
override fun onContextItemSelected(item: MenuItem): Boolean { override fun onContextItemSelected(item: MenuItem): Boolean {
val menuInfo = item.menuInfo val menuInfo = item.menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: run {
if (menuInfo is ExtendedRecyclerView.ContextMenuInfo) { return super.onContextItemSelected(item)
when (menuInfo.recyclerViewId) { }
R.id.attachedMediaPreview -> { when (menuInfo.recyclerViewId) {
val position = menuInfo.position R.id.attachedMediaPreview -> {
val altText = mediaPreviewAdapter.getItem(position).alt_text when (item.itemId) {
executeAfterFragmentResumed { activity -> R.id.edit_description -> {
EditAltTextDialogFragment.show(activity.supportFragmentManager, position, val position = menuInfo.position
altText) val altText = mediaPreviewAdapter.getItem(position).alt_text
executeAfterFragmentResumed { activity ->
EditAltTextDialogFragment.show(activity.supportFragmentManager, position,
altText)
}
} }
return true
} }
return true
} }
} }
return super.onContextItemSelected(item) return super.onContextItemSelected(item)

View File

@ -72,13 +72,15 @@ import org.mariotaku.ktextension.convert
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.twidere.Constants.* import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.iface.IControlBarActivity import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper
import org.mariotaku.twidere.adapter.SupportTabsAdapter import org.mariotaku.twidere.adapter.SupportTabsAdapter
import org.mariotaku.twidere.annotation.CustomTabType import org.mariotaku.twidere.annotation.CustomTabType
import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.constant.* import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.extension.applyTheme import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.fragment.* import org.mariotaku.twidere.fragment.AccountsDashboardFragment
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.CustomTabsFragment
import org.mariotaku.twidere.fragment.iface.IFloatingActionButtonFragment import org.mariotaku.twidere.fragment.iface.IFloatingActionButtonFragment
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface
import org.mariotaku.twidere.fragment.iface.SupportFragmentCallback import org.mariotaku.twidere.fragment.iface.SupportFragmentCallback
@ -87,7 +89,6 @@ import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.SupportTabSpec import org.mariotaku.twidere.model.SupportTabSpec
import org.mariotaku.twidere.model.Tab import org.mariotaku.twidere.model.Tab
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.TaskStateChangedEvent
import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent
import org.mariotaku.twidere.provider.TwidereDataStore.Activities import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
@ -112,7 +113,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
private var updateUnreadCountTask: UpdateUnreadCountTask? = null private var updateUnreadCountTask: UpdateUnreadCountTask? = null
private val readStateChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key -> updateUnreadCount() } private val readStateChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key -> updateUnreadCount() }
private val controlBarShowHideHelper = IControlBarActivity.ControlBarShowHideHelper(this) private val controlBarShowHideHelper = ControlBarShowHideHelper(this)
private val homeDrawerToggleDelegate = object : ActionBarDrawerToggle.Delegate { private val homeDrawerToggleDelegate = object : ActionBarDrawerToggle.Delegate {
override fun setActionBarUpIndicator(upDrawable: Drawable, @StringRes contentDescRes: Int) { override fun setActionBarUpIndicator(upDrawable: Drawable, @StringRes contentDescRes: Int) {
@ -172,133 +173,6 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
val leftDrawerFragment: Fragment? val leftDrawerFragment: Fragment?
get() = supportFragmentManager.findFragmentById(R.id.leftDrawer) get() = supportFragmentManager.findFragmentById(R.id.leftDrawer)
override fun getSystemWindowsInsets(insets: Rect): Boolean {
if (mainTabs == null || homeContent == null) return false
val height = mainTabs.height
if (height != 0) {
insets.top = height
} else {
insets.top = ThemeUtils.getActionBarHeight(this)
}
return true
}
override fun setControlBarVisibleAnimate(visible: Boolean, listener: IControlBarActivity.ControlBarShowHideHelper.ControlBarAnimationListener?) {
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (drawerToggle.onOptionsItemSelected(item)) {
return true
}
when (item.itemId) {
android.R.id.home -> {
val fm = supportFragmentManager
val count = fm.backStackEntryCount
if (homeMenu.isDrawerOpen(GravityCompat.START) || homeMenu.isDrawerOpen(GravityCompat.END)) {
homeMenu.closeDrawers()
return true
} else if (count == 0) {
homeMenu.openDrawer(GravityCompat.START)
return true
}
return true
}
R.id.search -> {
openSearchView(selectedAccountToSearch)
return true
}
R.id.actions -> {
triggerActionsClick()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
if (handleFragmentKeyboardShortcutSingle(handler, keyCode, event, metaState)) return true
var action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_HOME, keyCode, event, metaState)
if (action != null) {
when (action) {
KeyboardShortcutConstants.ACTION_HOME_ACCOUNTS_DASHBOARD -> {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {
homeMenu.closeDrawers()
} else {
homeMenu.openDrawer(GravityCompat.START)
setControlBarVisibleAnimate(true)
}
return true
}
}
}
action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
if (action != null) {
when (action) {
KeyboardShortcutConstants.ACTION_NAVIGATION_PREVIOUS_TAB -> {
val previous = mainPager.currentItem - 1
if (previous < 0 && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.START) != null) {
homeMenu.openDrawer(GravityCompat.START)
setControlBarVisibleAnimate(true)
} else if (previous < pagerAdapter.count) {
if (homeMenu.isDrawerOpen(GravityCompat.END)) {
homeMenu.closeDrawers()
} else {
mainPager.setCurrentItem(previous, true)
}
}
return true
}
KeyboardShortcutConstants.ACTION_NAVIGATION_NEXT_TAB -> {
val next = mainPager.currentItem + 1
if (next >= pagerAdapter.count && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.END) != null) {
homeMenu.openDrawer(GravityCompat.END)
setControlBarVisibleAnimate(true)
} else if (next >= 0) {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {
homeMenu.closeDrawers()
} else {
mainPager.setCurrentItem(next, true)
}
}
return true
}
}
}
return handler.handleKey(this, null, keyCode, event, metaState)
}
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
if (isFragmentKeyboardShortcutHandled(handler, keyCode, event, metaState)) return true
return super.isKeyboardShortcutHandled(handler, keyCode, event, metaState)
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int, repeatCount: Int, event: KeyEvent, metaState: Int): Boolean {
if (handleFragmentKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState))
return true
return super.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_MENU -> {
if (isDrawerOpen) {
homeMenu.closeDrawers()
} else {
homeMenu.openDrawer(GravityCompat.START)
}
return true
}
KeyEvent.KEYCODE_BACK -> {
if (isDrawerOpen) {
homeMenu.closeDrawers()
return true
}
}
}
return super.onKeyUp(keyCode, event)
}
private val isDrawerOpen: Boolean private val isDrawerOpen: Boolean
get() { get() {
val drawer = homeMenu ?: return false val drawer = homeMenu ?: return false
@ -405,6 +279,12 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
} }
} }
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState()
}
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
multiSelectHandler.dispatchOnStart() multiSelectHandler.dispatchOnStart()
@ -431,23 +311,33 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
super.onStop() super.onStop()
} }
fun notifyAccountsChanged() { override fun onDestroy() {
stopService(Intent(this, StreamingService::class.java))
// Delete unused items in databases.
val context = applicationContext
TaskStarter.execute(object : AbstractTask<Any?, Any?, Any?>() {
override fun doLongOperation(o: Any?): Any? {
DataStoreUtils.cleanDatabasesByItemLimit(context)
return null
}
})
super.onDestroy()
}
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
// Must exclude fragments not belongs tabs, otherwise it will crash
if (fragment !is AccountsDashboardFragment) {
updateActionsButton()
}
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
@Subscribe
fun notifyTaskStateChanged(event: TaskStateChangedEvent) {
updateActionsButton()
}
@Subscribe
fun notifyUnreadCountUpdated(event: UnreadCountUpdatedEvent) {
updateUnreadCount()
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v) { when (v) {
actionsButton -> { actionsButton -> {
@ -499,11 +389,6 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
return true return true
} }
fun openSearchView(account: AccountDetails?) {
selectedAccountToSearch = account
onSearchRequested()
}
override fun onFitSystemWindows(insets: Rect) { override fun onFitSystemWindows(insets: Rect) {
super.onFitSystemWindows(insets) super.onFitSystemWindows(insets)
val fragment = leftDrawerFragment val fragment = leftDrawerFragment
@ -512,6 +397,158 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
} }
} }
override fun onNewIntent(intent: Intent) {
val tabPosition = handleIntent(intent, false)
if (tabPosition >= 0) {
mainPager.currentItem = tabPosition.coerceInOr(0 until pagerAdapter.count, 0)
}
}
override fun getSystemWindowsInsets(insets: Rect): Boolean {
if (mainTabs == null || homeContent == null) return false
val height = mainTabs.height
if (height != 0) {
insets.top = height
} else {
insets.top = ThemeUtils.getActionBarHeight(this)
}
return true
}
override fun setControlBarVisibleAnimate(visible: Boolean,
listener: ControlBarShowHideHelper.ControlBarAnimationListener?) {
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (drawerToggle.onOptionsItemSelected(item)) {
return true
}
when (item.itemId) {
android.R.id.home -> {
val fm = supportFragmentManager
val count = fm.backStackEntryCount
if (homeMenu.isDrawerOpen(GravityCompat.START) || homeMenu.isDrawerOpen(GravityCompat.END)) {
homeMenu.closeDrawers()
return true
} else if (count == 0) {
homeMenu.openDrawer(GravityCompat.START)
return true
}
return true
}
R.id.search -> {
openSearchView(selectedAccountToSearch)
return true
}
R.id.actions -> {
triggerActionsClick()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler, keyCode: Int,
event: KeyEvent, metaState: Int): Boolean {
if (handleFragmentKeyboardShortcutSingle(handler, keyCode, event, metaState)) return true
var action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_HOME, keyCode, event, metaState)
if (action != null) {
when (action) {
KeyboardShortcutConstants.ACTION_HOME_ACCOUNTS_DASHBOARD -> {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {
homeMenu.closeDrawers()
} else {
homeMenu.openDrawer(GravityCompat.START)
setControlBarVisibleAnimate(true)
}
return true
}
}
}
action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
if (action != null) {
when (action) {
KeyboardShortcutConstants.ACTION_NAVIGATION_PREVIOUS_TAB -> {
val previous = mainPager.currentItem - 1
if (previous < 0 && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.START) != null) {
homeMenu.openDrawer(GravityCompat.START)
setControlBarVisibleAnimate(true)
} else if (previous < pagerAdapter.count) {
if (homeMenu.isDrawerOpen(GravityCompat.END)) {
homeMenu.closeDrawers()
} else {
mainPager.setCurrentItem(previous, true)
}
}
return true
}
KeyboardShortcutConstants.ACTION_NAVIGATION_NEXT_TAB -> {
val next = mainPager.currentItem + 1
if (next >= pagerAdapter.count && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.END) != null) {
homeMenu.openDrawer(GravityCompat.END)
setControlBarVisibleAnimate(true)
} else if (next >= 0) {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {
homeMenu.closeDrawers()
} else {
mainPager.setCurrentItem(next, true)
}
}
return true
}
}
}
return handler.handleKey(this, null, keyCode, event, metaState)
}
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int,
event: KeyEvent, metaState: Int): Boolean {
if (isFragmentKeyboardShortcutHandled(handler, keyCode, event, metaState)) return true
return super.isKeyboardShortcutHandled(handler, keyCode, event, metaState)
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int,
repeatCount: Int, event: KeyEvent, metaState: Int): Boolean {
if (handleFragmentKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState))
return true
return super.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_MENU -> {
if (isDrawerOpen) {
homeMenu.closeDrawers()
} else {
homeMenu.openDrawer(GravityCompat.START)
}
return true
}
KeyEvent.KEYCODE_BACK -> {
if (isDrawerOpen) {
homeMenu.closeDrawers()
return true
}
}
}
return super.onKeyUp(keyCode, event)
}
fun notifyAccountsChanged() {
}
@Subscribe
fun notifyUnreadCountUpdated(event: UnreadCountUpdatedEvent) {
updateUnreadCount()
}
fun openSearchView(account: AccountDetails?) {
selectedAccountToSearch = account
onSearchRequested()
}
fun updateUnreadCount() { fun updateUnreadCount() {
if (mainTabs == null || updateUnreadCountTask != null && updateUnreadCountTask!!.status == AsyncTask.Status.RUNNING) if (mainTabs == null || updateUnreadCountTask != null && updateUnreadCountTask!!.status == AsyncTask.Status.RUNNING)
return return
@ -524,34 +561,6 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
val tabs: List<SupportTabSpec> val tabs: List<SupportTabSpec>
get() = pagerAdapter.tabs get() = pagerAdapter.tabs
override fun onNewIntent(intent: Intent) {
val tabPosition = handleIntent(intent, false)
if (tabPosition >= 0) {
mainPager.currentItem = tabPosition.coerceInOr(0 until pagerAdapter.count, 0)
}
}
override fun onDestroy() {
stopService(Intent(this, StreamingService::class.java))
// Delete unused items in databases.
val context = applicationContext
TaskStarter.execute(object : AbstractTask<Any?, Any?, Any?>() {
override fun doLongOperation(o: Any?): Any? {
DataStoreUtils.cleanDatabasesByItemLimit(context)
return null
}
})
super.onDestroy()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
// Pass any configuration change to the drawer toggle // Pass any configuration change to the drawer toggle
@ -842,9 +851,15 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
} }
private fun updateActionsButton() { private fun updateActionsButton() {
val position = mainPager.currentItem val fragment = run {
if (pagerAdapter.count == 0) return if (pagerAdapter.count == 0) return@run null
val fragment = pagerAdapter.instantiateItem(mainPager, position) as? IFloatingActionButtonFragment val position = mainPager.currentItem
val f = pagerAdapter.instantiateItem(mainPager, position) as? IFloatingActionButtonFragment
if (f is Fragment && (f.isDetached || f.host == null)) {
return@run null
}
return@run f
}
val info = fragment?.getActionInfo("home") ?: run { val info = fragment?.getActionInfo("home") ?: run {
actionsButton.setImageResource(R.drawable.ic_action_status_compose) actionsButton.setImageResource(R.drawable.ic_action_status_compose)
actionsButton.contentDescription = getString(R.string.action_compose) actionsButton.contentDescription = getString(R.string.action_compose)

View File

@ -164,7 +164,11 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
} }
interface Listener { interface Listener {
fun onMediaClick(position: Int, media: ParcelableMedia, accountKey: UserKey?) fun onMediaClick(position: Int, media: ParcelableMedia, accountKey: UserKey?)
fun onMessageLongClick(position: Int, holder: RecyclerView.ViewHolder): Boolean
} }
companion object { companion object {

View File

@ -33,6 +33,7 @@ import android.support.v7.widget.RecyclerView
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import com.squareup.otto.Subscribe import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_premium_dashboard.*
import kotlinx.android.synthetic.main.fragment_messages_conversation.* import kotlinx.android.synthetic.main.fragment_messages_conversation.*
import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
@ -50,7 +51,9 @@ import org.mariotaku.twidere.adapter.MessagesConversationAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.newDocumentApiKey import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.extension.model.getSummaryText
import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.fragment.AbsContentListRecyclerViewFragment import org.mariotaku.twidere.fragment.AbsContentListRecyclerViewFragment
import org.mariotaku.twidere.fragment.EditAltTextDialogFragment import org.mariotaku.twidere.fragment.EditAltTextDialogFragment
@ -62,9 +65,11 @@ import org.mariotaku.twidere.model.event.SendMessageTaskEvent
import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.service.LengthyOperationsService import org.mariotaku.twidere.service.LengthyOperationsService
import org.mariotaku.twidere.task.GetMessagesTask
import org.mariotaku.twidere.task.compose.AbsAddMediaTask import org.mariotaku.twidere.task.compose.AbsAddMediaTask
import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask import org.mariotaku.twidere.task.compose.AbsDeleteMediaTask
import org.mariotaku.twidere.task.twitter.message.DestroyMessageTask
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.util.ClipboardUtils
import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.IntentUtils import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.PreviewGridItemDecoration import org.mariotaku.twidere.util.PreviewGridItemDecoration
@ -108,6 +113,10 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
media = message.media, current = media, media = message.media, current = media,
newDocument = preferences[newDocumentApiKey], message = message) newDocument = preferences[newDocumentApiKey], message = message)
} }
override fun onMessageLongClick(position: Int, holder: RecyclerView.ViewHolder): Boolean {
return recyclerView.showContextMenuForChild(holder.itemView)
}
} }
mediaPreviewAdapter = MediaPreviewAdapter(context) mediaPreviewAdapter = MediaPreviewAdapter(context)
@ -125,6 +134,8 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
attachedMediaPreview.layoutManager = FixedLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) attachedMediaPreview.layoutManager = FixedLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
attachedMediaPreview.adapter = mediaPreviewAdapter attachedMediaPreview.adapter = mediaPreviewAdapter
attachedMediaPreview.addItemDecoration(PreviewGridItemDecoration(resources.getDimensionPixelSize(R.dimen.element_spacing_small))) attachedMediaPreview.addItemDecoration(PreviewGridItemDecoration(resources.getDimensionPixelSize(R.dimen.element_spacing_small)))
registerForContextMenu(recyclerView)
registerForContextMenu(attachedMediaPreview) registerForContextMenu(attachedMediaPreview)
sendMessage.setOnClickListener { sendMessage.setOnClickListener {
@ -220,25 +231,53 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
} }
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo) {
if (v === attachedMediaPreview) { if (menuInfo !is ExtendedRecyclerView.ContextMenuInfo) return
menu.setHeaderTitle(R.string.edit_media) when (menuInfo.recyclerViewId) {
activity.menuInflater.inflate(R.menu.menu_attached_media_edit, menu) R.id.recyclerView -> {
val message = adapter.getMessage(menuInfo.position) ?: return
val conversation = adapter.conversation
menu.setHeaderTitle(message.getSummaryText(context, userColorNameManager, conversation,
preferences[nameFirstKey]))
activity.menuInflater.inflate(R.menu.menu_conversation_message_item, menu)
}
R.id.attachedMediaPreview -> {
menu.setHeaderTitle(R.string.edit_media)
activity.menuInflater.inflate(R.menu.menu_attached_media_edit, menu)
}
} }
} }
override fun onContextItemSelected(item: MenuItem): Boolean { override fun onContextItemSelected(item: MenuItem): Boolean {
val menuInfo = item.menuInfo val menuInfo = item.menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: run {
if (menuInfo is ExtendedRecyclerView.ContextMenuInfo) { return super.onContextItemSelected(item)
when (menuInfo.recyclerViewId) { }
R.id.attachedMediaPreview -> { when (menuInfo.recyclerViewId) {
val position = menuInfo.position R.id.recyclerView -> {
val altText = mediaPreviewAdapter.getItem(position).alt_text val message = adapter.getMessage(menuInfo.position) ?: return true
executeAfterFragmentResumed { fragment -> when (item.itemId) {
EditAltTextDialogFragment.show(fragment.childFragmentManager, position, R.id.copy -> {
altText) ClipboardUtils.setText(context, message.text_unescaped)
}
R.id.delete -> {
val task = DestroyMessageTask(context, message.account_key,
message.conversation_id, message.id)
TaskStarter.execute(task)
} }
return true
} }
return true
}
R.id.attachedMediaPreview -> {
when (item.itemId) {
R.id.edit_description -> {
val position = menuInfo.position
val altText = mediaPreviewAdapter.getItem(position).alt_text
executeAfterFragmentResumed { fragment ->
EditAltTextDialogFragment.show(fragment.childFragmentManager, position,
altText)
}
}
}
return true
} }
} }
return super.onContextItemSelected(item) return super.onContextItemSelected(item)

View File

@ -47,7 +47,7 @@ import org.mariotaku.twidere.model.ParcelableMessageConversationCursorIndices
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.GetMessagesTaskEvent import org.mariotaku.twidere.model.event.GetMessagesTaskEvent
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.task.GetMessagesTask import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.ErrorInfoStore import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.IntentUtils import org.mariotaku.twidere.util.IntentUtils

View File

@ -62,7 +62,7 @@ import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.task.CreateFavoriteTask import org.mariotaku.twidere.task.CreateFavoriteTask
import org.mariotaku.twidere.task.RetweetStatusTask import org.mariotaku.twidere.task.RetweetStatusTask
import org.mariotaku.twidere.task.message.SendMessageTask import org.mariotaku.twidere.task.twitter.message.SendMessageTask
import org.mariotaku.twidere.task.twitter.UpdateStatusTask import org.mariotaku.twidere.task.twitter.UpdateStatusTask
import org.mariotaku.twidere.util.NotificationManagerWrapper import org.mariotaku.twidere.util.NotificationManagerWrapper
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils

View File

@ -1,83 +0,0 @@
package org.mariotaku.twidere.task
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.support.v4.util.ArraySet
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.Trends
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.model.ParcelableTrend
import org.mariotaku.twidere.model.ParcelableTrendValuesCreator
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.TrendsRefreshedEvent
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
/**
* Created by mariotaku on 16/2/24.
*/
class GetTrendsTask(
context: Context,
private val accountKey: UserKey,
private val woeId: Int
) : BaseAbstractTask<Any?, Unit, Any?>(context) {
override fun doLongOperation(param: Any?) {
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: return
val twitter = details.newMicroBlogInstance(context, cls = MicroBlog::class.java)
try {
val trends = when {
details.type == AccountType.FANFOU -> twitter.fanfouTrends
else -> twitter.getLocationTrends(woeId).firstOrNull()
} ?: return
storeTrends(context.contentResolver, CachedTrends.Local.CONTENT_URI, trends)
} catch (e: MicroBlogException) {
DebugLog.w(LOGTAG, tr = e)
}
}
override fun afterExecute(handler: Any?, result: Unit) {
bus.post(TrendsRefreshedEvent())
}
private fun storeTrends(cr: ContentResolver, uri: Uri, trends: Trends) {
val hashtags = ArraySet<String>()
val deleteWhere = Expression.and(Expression.equalsArgs(CachedTrends.ACCOUNT_KEY),
Expression.equalsArgs(CachedTrends.WOEID)).sql
val deleteWhereArgs = arrayOf(accountKey.toString(), woeId.toString())
cr.delete(CachedTrends.Local.CONTENT_URI, deleteWhere, deleteWhereArgs)
val allTrends = ArrayList<ParcelableTrend>()
trends.trends.forEachIndexed { idx, trend ->
val hashtag = trend.name.replaceFirst("#", "")
hashtags.add(hashtag)
allTrends.add(ParcelableTrend().apply {
this.account_key = accountKey
this.woe_id = woeId
this.name = trend.name
this.timestamp = System.currentTimeMillis()
this.trend_order = idx
})
}
ContentResolverUtils.bulkInsert(cr, uri, allTrends.map(ParcelableTrendValuesCreator::create))
ContentResolverUtils.bulkDelete(cr, CachedHashtags.CONTENT_URI, CachedHashtags.NAME, false,
hashtags, null, null)
ContentResolverUtils.bulkInsert(cr, CachedHashtags.CONTENT_URI, hashtags.map {
val values = ContentValues()
values.put(CachedHashtags.NAME, it)
return@map values
})
}
}

View File

@ -1,4 +1,23 @@
package org.mariotaku.twidere.task /*
* 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.task.twitter
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri

View File

@ -1,4 +1,23 @@
package org.mariotaku.twidere.task /*
* 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.task.twitter
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri

View File

@ -1,4 +1,23 @@
package org.mariotaku.twidere.task /*
* 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.task.twitter
import android.content.Context import android.content.Context
import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.abstask.library.AbstractTask

View File

@ -0,0 +1,102 @@
/*
* 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.task.twitter
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.support.v4.util.ArraySet
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.Trends
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.model.ParcelableTrend
import org.mariotaku.twidere.model.ParcelableTrendValuesCreator
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.TrendsRefreshedEvent
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
/**
* Created by mariotaku on 16/2/24.
*/
class GetTrendsTask(
context: android.content.Context,
private val accountKey: org.mariotaku.twidere.model.UserKey,
private val woeId: Int
) : org.mariotaku.twidere.task.BaseAbstractTask<Any?, Unit, Any?>(context) {
override fun doLongOperation(param: Any?) {
val details = org.mariotaku.twidere.model.util.AccountUtils.getAccountDetails(android.accounts.AccountManager.get(context), accountKey, true) ?: return
val twitter = details.newMicroBlogInstance(context, cls = org.mariotaku.microblog.library.MicroBlog::class.java)
try {
val trends = when {
details.type == org.mariotaku.twidere.annotation.AccountType.FANFOU -> twitter.fanfouTrends
else -> twitter.getLocationTrends(woeId).firstOrNull()
} ?: return
storeTrends(context.contentResolver, org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends.Local.CONTENT_URI, trends)
} catch (e: org.mariotaku.microblog.library.MicroBlogException) {
org.mariotaku.twidere.util.DebugLog.w(LOGTAG, tr = e)
}
}
override fun afterExecute(handler: Any?, result: Unit) {
bus.post(org.mariotaku.twidere.model.event.TrendsRefreshedEvent())
}
private fun storeTrends(cr: android.content.ContentResolver, uri: android.net.Uri, trends: org.mariotaku.microblog.library.twitter.model.Trends) {
val hashtags = android.support.v4.util.ArraySet<String>()
val deleteWhere = org.mariotaku.sqliteqb.library.Expression.and(org.mariotaku.sqliteqb.library.Expression.equalsArgs(org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends.ACCOUNT_KEY),
org.mariotaku.sqliteqb.library.Expression.equalsArgs(org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends.WOEID)).sql
val deleteWhereArgs = arrayOf(accountKey.toString(), woeId.toString())
cr.delete(org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends.Local.CONTENT_URI, deleteWhere, deleteWhereArgs)
val allTrends = java.util.ArrayList<org.mariotaku.twidere.model.ParcelableTrend>()
trends.trends.forEachIndexed { idx, trend ->
val hashtag = trend.name.replaceFirst("#", "")
hashtags.add(hashtag)
allTrends.add(org.mariotaku.twidere.model.ParcelableTrend().apply {
this.account_key = accountKey
this.woe_id = woeId
this.name = trend.name
this.timestamp = System.currentTimeMillis()
this.trend_order = idx
})
}
org.mariotaku.twidere.util.content.ContentResolverUtils.bulkInsert(cr, uri, allTrends.map(org.mariotaku.twidere.model.ParcelableTrendValuesCreator::create))
ContentResolverUtils.bulkDelete(cr, CachedHashtags.CONTENT_URI, CachedHashtags.NAME, false,
hashtags, null, null)
ContentResolverUtils.bulkInsert(cr, CachedHashtags.CONTENT_URI, hashtags.map {
val values = ContentValues()
values.put(CachedHashtags.NAME, it)
return@map values
})
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.mariotaku.twidere.task.message package org.mariotaku.twidere.task.twitter.message
import android.content.Context import android.content.Context
import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.MicroBlogException

View File

@ -0,0 +1,82 @@
/*
* 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.task.twitter.message
import android.accounts.AccountManager
import android.content.Context
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask
/**
* Created by mariotaku on 2017/2/16.
*/
class DestroyMessageTask(
context: Context,
val accountKey: UserKey,
val conversationId: String?,
val messageId: String
) : ExceptionHandlingAbstractTask<Unit?, Boolean, MicroBlogException, Unit?>(context) {
override fun onExecute(params: Unit?): Boolean {
val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?:
throw MicroBlogException("No account")
val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java)
if (!performDestroyMessage(microBlog, account)) {
return false
}
val deleteWhere: String
val deleteWhereArgs: Array<String>
if (conversationId != null) {
deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
Expression.equalsArgs(Messages.CONVERSATION_ID),
Expression.equalsArgs(Messages.MESSAGE_ID)).sql
deleteWhereArgs = arrayOf(accountKey.toString(), conversationId, messageId)
} else {
deleteWhere = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
Expression.equalsArgs(Messages.MESSAGE_ID)).sql
deleteWhereArgs = arrayOf(accountKey.toString(), messageId)
}
context.contentResolver.delete(Messages.CONTENT_URI, deleteWhere, deleteWhereArgs)
return true
}
private fun performDestroyMessage(microBlog: MicroBlog, account: AccountDetails): Boolean {
when (account.type) {
AccountType.TWITTER -> {
if (account.isOfficial(context)) {
return microBlog.destroyDm(messageId).isSuccessful
}
}
}
microBlog.destroyDirectMessage(messageId)
return true
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.mariotaku.twidere.task.message package org.mariotaku.twidere.task.twitter.message
import android.content.Context import android.content.Context
import org.mariotaku.ktextension.isNotNullOrEmpty import org.mariotaku.ktextension.isNotNullOrEmpty
@ -38,9 +38,9 @@ import org.mariotaku.twidere.model.event.SendMessageTaskEvent
import org.mariotaku.twidere.model.util.ParcelableMessageUtils import org.mariotaku.twidere.model.util.ParcelableMessageUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask
import org.mariotaku.twidere.task.GetMessagesTask import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.task.GetMessagesTask.Companion.addConversation import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addConversation
import org.mariotaku.twidere.task.GetMessagesTask.Companion.addLocalConversations import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addLocalConversations
import org.mariotaku.twidere.task.twitter.UpdateStatusTask import org.mariotaku.twidere.task.twitter.UpdateStatusTask
/** /**

View File

@ -1,35 +1,45 @@
package org.mariotaku.twidere.task /*
* 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.task.twitter.message
import android.accounts.AccountManager
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import org.mariotaku.ktextension.toInt import org.mariotaku.ktextension.toInt
import org.mariotaku.ktextension.useCursor import org.mariotaku.ktextension.useCursor
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.DMResponse import org.mariotaku.microblog.library.twitter.model.DMResponse
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.User import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.microblog.library.twitter.model.fixMedia import org.mariotaku.microblog.library.twitter.model.fixMedia
import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.applyFrom import org.mariotaku.twidere.extension.model.applyFrom
import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.timestamp import org.mariotaku.twidere.extension.model.timestamp
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
import org.mariotaku.twidere.model.event.GetMessagesTaskEvent
import org.mariotaku.twidere.model.message.conversation.TwitterOfficialConversationExtras import org.mariotaku.twidere.model.message.conversation.TwitterOfficialConversationExtras
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.AccountUtils.getAccountDetails import org.mariotaku.twidere.model.util.AccountUtils.getAccountDetails
import org.mariotaku.twidere.model.util.ParcelableMessageUtils
import org.mariotaku.twidere.model.util.ParcelableUserUtils import org.mariotaku.twidere.model.util.ParcelableUserUtils
import org.mariotaku.twidere.model.util.UserKeyUtils import org.mariotaku.twidere.model.util.UserKeyUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Messages import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.content.ContentResolverUtils import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.* import java.util.*
@ -38,35 +48,35 @@ import java.util.*
*/ */
class GetMessagesTask( class GetMessagesTask(
context: Context context: android.content.Context
) : BaseAbstractTask<GetMessagesTask.RefreshMessagesTaskParam, Unit, (Boolean) -> Unit>(context) { ) : org.mariotaku.twidere.task.BaseAbstractTask<GetMessagesTask.RefreshMessagesTaskParam, Unit, (Boolean) -> Unit>(context) {
override fun doLongOperation(param: RefreshMessagesTaskParam) { override fun doLongOperation(param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam) {
val accountKeys = param.accountKeys val accountKeys = param.accountKeys
val am = AccountManager.get(context) val am = android.accounts.AccountManager.get(context)
accountKeys.forEachIndexed { i, accountKey -> accountKeys.forEachIndexed { i, accountKey ->
val details = getAccountDetails(am, accountKey, true) ?: return@forEachIndexed val details = getAccountDetails(am, accountKey, true) ?: return@forEachIndexed
val microBlog = details.newMicroBlogInstance(context, true, cls = MicroBlog::class.java) val microBlog = details.newMicroBlogInstance(context, true, cls = org.mariotaku.microblog.library.MicroBlog::class.java)
val messages = try { val messages = try {
getMessages(microBlog, details, param, i) getMessages(microBlog, details, param, i)
} catch (e: MicroBlogException) { } catch (e: org.mariotaku.microblog.library.MicroBlogException) {
return@forEachIndexed return@forEachIndexed
} }
storeMessages(context, messages, details) org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.storeMessages(context, messages, details)
} }
} }
override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Unit) { override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Unit) {
callback?.invoke(true) callback?.invoke(true)
bus.post(GetMessagesTaskEvent(Messages.CONTENT_URI, params?.taskTag, false, null)) bus.post(org.mariotaku.twidere.model.event.GetMessagesTaskEvent(org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI, params?.taskTag, false, null))
} }
private fun getMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { private fun getMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
when (details.type) { when (details.type) {
AccountType.FANFOU -> { org.mariotaku.twidere.annotation.AccountType.FANFOU -> {
// Use fanfou DM api, disabled since it's conversation api is not suitable for paging // Use fanfou DM api, disabled since it's conversation api is not suitable for paging
// return getFanfouMessages(microBlog, details, param, index) // return getFanfouMessages(microBlog, details, param, index)
} }
AccountType.TWITTER -> { org.mariotaku.twidere.annotation.AccountType.TWITTER -> {
// Use official DM api // Use official DM api
if (details.isOfficial(context)) { if (details.isOfficial(context)) {
return getTwitterOfficialMessages(microBlog, details, param, index) return getTwitterOfficialMessages(microBlog, details, param, index)
@ -77,8 +87,8 @@ class GetMessagesTask(
return getDefaultMessages(microBlog, details, param, index) return getDefaultMessages(microBlog, details, param, index)
} }
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails, private fun getTwitterOfficialMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val conversationId = param.conversationId val conversationId = param.conversationId
if (conversationId == null) { if (conversationId == null) {
return getTwitterOfficialUserInbox(microBlog, details, param, index) return getTwitterOfficialUserInbox(microBlog, details, param, index)
@ -87,22 +97,22 @@ class GetMessagesTask(
} }
} }
private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { private fun getFanfouMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val conversationId = param.conversationId val conversationId = param.conversationId
if (conversationId == null) { if (conversationId == null) {
return getFanfouConversations(microBlog, details, param, index) return getFanfouConversations(microBlog, details, param, index)
} else { } else {
return DatabaseUpdateData(emptyList(), emptyList()) return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
} }
} }
private fun getDefaultMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { private fun getDefaultMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val accountKey = details.key val accountKey = details.key
val sinceIds = if (param.hasSinceIds) param.sinceIds else null val sinceIds = if (param.hasSinceIds) param.sinceIds else null
val maxIds = if (param.hasMaxIds) param.maxIds else null val maxIds = if (param.hasMaxIds) param.maxIds else null
val received = microBlog.getDirectMessages(Paging().apply { val received = microBlog.getDirectMessages(org.mariotaku.microblog.library.twitter.model.Paging().apply {
count(100) count(100)
val maxId = maxIds?.get(index) val maxId = maxIds?.get(index)
val sinceId = sinceIds?.get(index) val sinceId = sinceIds?.get(index)
@ -113,7 +123,7 @@ class GetMessagesTask(
sinceId(sinceId) sinceId(sinceId)
} }
}) })
val sent = microBlog.getSentDirectMessages(Paging().apply { val sent = microBlog.getSentDirectMessages(org.mariotaku.microblog.library.twitter.model.Paging().apply {
count(100) count(100)
val accountsCount = param.accountKeys.size val accountsCount = param.accountKeys.size
val maxId = maxIds?.get(accountsCount + index) val maxId = maxIds?.get(accountsCount + index)
@ -127,75 +137,75 @@ class GetMessagesTask(
}) })
val insertMessages = arrayListOf<ParcelableMessage>() val insertMessages = arrayListOf<org.mariotaku.twidere.model.ParcelableMessage>()
val conversations = hashMapOf<String, ParcelableMessageConversation>() val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
val conversationIds = hashSetOf<String>() val conversationIds = hashSetOf<String>()
received.forEach { received.forEach {
conversationIds.add(ParcelableMessageUtils.incomingConversationId(it.senderId, it.recipientId)) conversationIds.add(org.mariotaku.twidere.model.util.ParcelableMessageUtils.incomingConversationId(it.senderId, it.recipientId))
} }
sent.forEach { sent.forEach {
conversationIds.add(ParcelableMessageUtils.outgoingConversationId(it.senderId, it.recipientId)) conversationIds.add(org.mariotaku.twidere.model.util.ParcelableMessageUtils.outgoingConversationId(it.senderId, it.recipientId))
} }
conversations.addLocalConversations(context, accountKey, conversationIds) conversations.addLocalConversations(context, accountKey, conversationIds)
received.forEachIndexed { i, dm -> received.forEachIndexed { i, dm ->
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, false, val message = org.mariotaku.twidere.model.util.ParcelableMessageUtils.fromMessage(accountKey, dm, false,
1.0 - (i.toDouble() / received.size)) 1.0 - (i.toDouble() / received.size))
insertMessages.add(message) insertMessages.add(message)
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient)) conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
} }
sent.forEachIndexed { i, dm -> sent.forEachIndexed { i, dm ->
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, true, val message = org.mariotaku.twidere.model.util.ParcelableMessageUtils.fromMessage(accountKey, dm, true,
1.0 - (i.toDouble() / sent.size)) 1.0 - (i.toDouble() / sent.size))
insertMessages.add(message) insertMessages.add(message)
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient)) conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
} }
return DatabaseUpdateData(conversations.values, insertMessages) return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(conversations.values, insertMessages)
} }
private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails, private fun getTwitterOfficialConversation(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
conversationId: String, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { conversationId: String, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val maxId = param.maxIds?.get(index) ?: return DatabaseUpdateData(emptyList(), emptyList()) val maxId = param.maxIds?.get(index) ?: return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
val paging = Paging().apply { val paging = org.mariotaku.microblog.library.twitter.model.Paging().apply {
maxId(maxId) maxId(maxId)
} }
val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline
response.fixMedia(microBlog) response.fixMedia(microBlog)
return createDatabaseUpdateData(context, details, response) return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.createDatabaseUpdateData(context, details, response)
} }
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, private fun getTwitterOfficialUserInbox(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
val cursor = if (param.hasCursors) param.cursors?.get(index) else null val cursor = if (param.hasCursors) param.cursors?.get(index) else null
val response = if (cursor != null) { val response = if (cursor != null) {
microBlog.getUserUpdates(cursor).userEvents microBlog.getUserUpdates(cursor).userEvents
} else { } else {
microBlog.getUserInbox(Paging().apply { microBlog.getUserInbox(org.mariotaku.microblog.library.twitter.model.Paging().apply {
if (maxId != null) { if (maxId != null) {
maxId(maxId) maxId(maxId)
} }
}).userInbox }).userInbox
} }
response.fixMedia(microBlog) response.fixMedia(microBlog)
return createDatabaseUpdateData(context, details, response) return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.createDatabaseUpdateData(context, details, response)
} }
private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { private fun getFanfouConversations(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val accountKey = details.key val accountKey = details.key
val cursor = param.cursors?.get(index) val cursor = param.cursors?.get(index)
val page = cursor?.substringAfter("page:").toInt(-1) val page = cursor?.substringAfter("page:").toInt(-1)
val result = microBlog.getConversationList(Paging().apply { val result = microBlog.getConversationList(org.mariotaku.microblog.library.twitter.model.Paging().apply {
count(60) count(60)
if (page >= 0) { if (page >= 0) {
page(page) page(page)
} }
}) })
val conversations = hashMapOf<String, ParcelableMessageConversation>() val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
val conversationIds = hashSetOf<String>() val conversationIds = hashSetOf<String>()
result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" } result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" }
@ -203,32 +213,32 @@ class GetMessagesTask(
result.forEachIndexed { i, item -> result.forEachIndexed { i, item ->
val dm = item.dm val dm = item.dm
// Sender is our self, treat as outgoing message // Sender is our self, treat as outgoing message
val message = ParcelableMessageUtils.fromMessage(accountKey, dm, dm.senderId == accountKey.id, val message = org.mariotaku.twidere.model.util.ParcelableMessageUtils.fromMessage(accountKey, dm, dm.senderId == accountKey.id,
1.0 - (i.toDouble() / result.size)) 1.0 - (i.toDouble() / result.size))
val mc = conversations.addConversation(message.conversation_id, details, message, val mc = conversations.addConversation(message.conversation_id, details, message,
setOf(dm.sender, dm.recipient)) setOf(dm.sender, dm.recipient))
mc.request_cursor = "page:$page" mc.request_cursor = "page:$page"
} }
return DatabaseUpdateData(conversations.values, emptyList()) return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(conversations.values, emptyList())
} }
data class DatabaseUpdateData( data class DatabaseUpdateData(
val conversations: Collection<ParcelableMessageConversation>, val conversations: Collection<org.mariotaku.twidere.model.ParcelableMessageConversation>,
val messages: Collection<ParcelableMessage>, val messages: Collection<org.mariotaku.twidere.model.ParcelableMessage>,
val deleteConversations: List<String> = emptyList(), val deleteConversations: List<String> = emptyList(),
val deleteMessages: Map<String, List<String>> = emptyMap(), val deleteMessages: Map<String, List<String>> = emptyMap(),
val conversationRequestCursor: String? = null val conversationRequestCursor: String? = null
) )
abstract class RefreshNewTaskParam( abstract class RefreshNewTaskParam(
context: Context context: android.content.Context
) : RefreshMessagesTaskParam(context) { ) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
override val sinceIds: Array<String?>? override val sinceIds: Array<String?>?
get() { get() {
val incomingIds = DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI, val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
defaultKeys, false) defaultKeys, false)
val outgoingIds = DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI, val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
defaultKeys, true) defaultKeys, true)
return incomingIds + outgoingIds return incomingIds + outgoingIds
} }
@ -236,8 +246,8 @@ class GetMessagesTask(
override val cursors: Array<String?>? override val cursors: Array<String?>?
get() { get() {
val cursors = arrayOfNulls<String>(defaultKeys.size) val cursors = arrayOfNulls<String>(defaultKeys.size)
val newestConversations = DataStoreUtils.getNewestConversations(context, val newestConversations = org.mariotaku.twidere.util.DataStoreUtils.getNewestConversations(context,
Messages.Conversations.CONTENT_URI, twitterOfficialKeys) org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations.CONTENT_URI, twitterOfficialKeys)
newestConversations.forEachIndexed { i, conversation -> newestConversations.forEachIndexed { i, conversation ->
cursors[i] = conversation?.request_cursor cursors[i] = conversation?.request_cursor
} }
@ -250,18 +260,18 @@ class GetMessagesTask(
} }
abstract class LoadMoreEntriesTaskParam( abstract class LoadMoreEntriesTaskParam(
context: Context context: android.content.Context
) : RefreshMessagesTaskParam(context) { ) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
override val maxIds: Array<String?>? by lazy { override val maxIds: Array<String?>? by lazy {
val incomingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI, val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
defaultKeys, false) defaultKeys, false)
val outgoingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI, val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
defaultKeys, true) defaultKeys, true)
val oldestConversations = DataStoreUtils.getOldestConversations(context, val oldestConversations = org.mariotaku.twidere.util.DataStoreUtils.getOldestConversations(context,
Messages.Conversations.CONTENT_URI, twitterOfficialKeys) org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations.CONTENT_URI, twitterOfficialKeys)
oldestConversations.forEachIndexed { i, conversation -> oldestConversations.forEachIndexed { i, conversation ->
val extras = conversation?.conversation_extras as? TwitterOfficialConversationExtras ?: return@forEachIndexed val extras = conversation?.conversation_extras as? org.mariotaku.twidere.model.message.conversation.TwitterOfficialConversationExtras ?: return@forEachIndexed
incomingIds[i] = extras.maxEntryId incomingIds[i] = extras.maxEntryId
} }
return@lazy incomingIds + outgoingIds return@lazy incomingIds + outgoingIds
@ -272,19 +282,19 @@ class GetMessagesTask(
} }
class LoadMoreMessageTaskParam( class LoadMoreMessageTaskParam(
context: Context, context: android.content.Context,
accountKey: UserKey, accountKey: org.mariotaku.twidere.model.UserKey,
override val conversationId: String, override val conversationId: String,
maxId: String maxId: String
) : RefreshMessagesTaskParam(context) { ) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
override val accountKeys: Array<UserKey> = arrayOf(accountKey) override val accountKeys: Array<org.mariotaku.twidere.model.UserKey> = arrayOf(accountKey)
override val maxIds: Array<String?>? = arrayOf(maxId) override val maxIds: Array<String?>? = arrayOf(maxId)
override val hasMaxIds: Boolean = true override val hasMaxIds: Boolean = true
} }
abstract class RefreshMessagesTaskParam( abstract class RefreshMessagesTaskParam(
val context: Context val context: android.content.Context
) : SimpleRefreshTaskParam() { ) : org.mariotaku.twidere.model.SimpleRefreshTaskParam() {
/** /**
* If `conversationId` has value, load messages in conversationId * If `conversationId` has value, load messages in conversationId
@ -293,21 +303,21 @@ class GetMessagesTask(
var taskTag: String? = null var taskTag: String? = null
protected val accounts: Array<AccountDetails?> by lazy { protected val accounts: Array<org.mariotaku.twidere.model.AccountDetails?> by lazy {
AccountUtils.getAllAccountDetails(AccountManager.get(context), accountKeys, false) org.mariotaku.twidere.model.util.AccountUtils.getAllAccountDetails(android.accounts.AccountManager.get(context), accountKeys, false)
} }
protected val defaultKeys: Array<UserKey?>by lazy { protected val defaultKeys: Array<org.mariotaku.twidere.model.UserKey?>by lazy {
return@lazy accounts.map { account -> return@lazy accounts.map { account ->
account ?: return@map null account ?: return@map null
if (account.isOfficial(context) || account.type == AccountType.FANFOU) { if (account.isOfficial(context) || account.type == org.mariotaku.twidere.annotation.AccountType.FANFOU) {
return@map null return@map null
} }
return@map account.key return@map account.key
}.toTypedArray() }.toTypedArray()
} }
protected val twitterOfficialKeys: Array<UserKey?> by lazy { protected val twitterOfficialKeys: Array<org.mariotaku.twidere.model.UserKey?> by lazy {
return@lazy accounts.map { account -> return@lazy accounts.map { account ->
account ?: return@map null account ?: return@map null
if (!account.isOfficial(context)) { if (!account.isOfficial(context)) {
@ -321,22 +331,22 @@ class GetMessagesTask(
companion object { companion object {
fun createDatabaseUpdateData(context: Context, account: AccountDetails, response: DMResponse): fun createDatabaseUpdateData(context: android.content.Context, account: org.mariotaku.twidere.model.AccountDetails, response: org.mariotaku.microblog.library.twitter.model.DMResponse):
DatabaseUpdateData { org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
val respConversations = response.conversations.orEmpty() val respConversations = response.conversations.orEmpty()
val respEntries = response.entries.orEmpty() val respEntries = response.entries.orEmpty()
val respUsers = response.users.orEmpty() val respUsers = response.users.orEmpty()
val conversations = hashMapOf<String, ParcelableMessageConversation>() val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
conversations.addLocalConversations(context, account.key, respConversations.keys) conversations.addLocalConversations(context, account.key, respConversations.keys)
val messages = ArrayList<ParcelableMessage>() val messages = java.util.ArrayList<org.mariotaku.twidere.model.ParcelableMessage>()
val messageDeletionsMap = HashMap<String, ArrayList<String>>() val messageDeletionsMap = java.util.HashMap<String, java.util.ArrayList<String>>()
val conversationDeletions = ArrayList<String>() val conversationDeletions = java.util.ArrayList<String>()
respEntries.mapNotNullTo(messages) { entry -> respEntries.mapNotNullTo(messages) { entry ->
when { when {
entry.messageDelete != null -> { entry.messageDelete != null -> {
val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { ArrayList<String>() } val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { java.util.ArrayList<String>() }
entry.messageDelete.messages?.forEach { entry.messageDelete.messages?.forEach {
list.add(it.messageId) list.add(it.messageId)
} }
@ -347,11 +357,11 @@ class GetMessagesTask(
return@mapNotNullTo null return@mapNotNullTo null
} }
else -> { else -> {
return@mapNotNullTo ParcelableMessageUtils.fromEntry(account.key, entry, respUsers) return@mapNotNullTo org.mariotaku.twidere.model.util.ParcelableMessageUtils.fromEntry(account.key, entry, respUsers)
} }
} }
} }
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id) val messagesMap = messages.groupBy(org.mariotaku.twidere.model.ParcelableMessage::conversation_id)
for ((k, v) in respConversations) { for ((k, v) in respConversations) {
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
val participants = respUsers.filterKeys { userId -> val participants = respUsers.filterKeys { userId ->

View File

@ -45,6 +45,11 @@ import org.mariotaku.twidere.model.event.*
import org.mariotaku.twidere.model.util.ParcelableUserListUtils import org.mariotaku.twidere.model.util.ParcelableUserListUtils
import org.mariotaku.twidere.provider.TwidereDataStore.* import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.task.* import org.mariotaku.twidere.task.*
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
import org.mariotaku.twidere.task.twitter.GetHomeTimelineTask
import org.mariotaku.twidere.task.twitter.GetSavedSearchesTask
import org.mariotaku.twidere.task.twitter.GetTrendsTask
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.util.collection.CompactHashSet import org.mariotaku.twidere.util.collection.CompactHashSet
import java.util.* import java.util.*

View File

@ -15,9 +15,9 @@ import org.mariotaku.twidere.model.SimpleRefreshTaskParam
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Activities import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.task.GetActivitiesAboutMeTask import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
import org.mariotaku.twidere.task.GetHomeTimelineTask import org.mariotaku.twidere.task.twitter.GetHomeTimelineTask
import org.mariotaku.twidere.task.GetMessagesTask import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.task.filter.RefreshFiltersSubscriptionsTask import org.mariotaku.twidere.task.filter.RefreshFiltersSubscriptionsTask
/** /**

View File

@ -53,6 +53,11 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
val textSize = adapter.textSize val textSize = adapter.textSize
text.textSize = textSize text.textSize = textSize
mediaPreview.style = adapter.mediaPreviewStyle mediaPreview.style = adapter.mediaPreviewStyle
messageBubble.setOnLongClickListener {
val listener = adapter.listener ?: return@setOnLongClickListener false
return@setOnLongClickListener listener.onMessageLongClick(layoutPosition, this)
}
} }
override fun display(message: ParcelableMessage, showDate: Boolean) { override fun display(message: ParcelableMessage, showDate: Boolean) {

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/copy"
android:title="@android:string/copy"/>
<item
android:id="@+id/delete"
android:title="@string/action_delete"/>
</menu>