improved hashtag auto completion
supports double hashtag for fanfou
This commit is contained in:
parent
a3adb9a0aa
commit
0eb13ade81
|
@ -90,7 +90,11 @@ class ComposeAutoCompleteAdapter(context: Context, val requestManager: RequestMa
|
|||
|
||||
icon.clearColorFilter()
|
||||
} else {
|
||||
text1.spannable = "#${cursor.getString(indices.title)}"
|
||||
text1.spannable = if (account?.type == AccountType.FANFOU) {
|
||||
"#${cursor.getString(indices.title)}#"
|
||||
} else {
|
||||
"#${cursor.getString(indices.title)}"
|
||||
}
|
||||
text2.setText(R.string.hashtag)
|
||||
|
||||
icon.setImageResource(R.drawable.ic_action_hashtag)
|
||||
|
@ -111,6 +115,9 @@ class ComposeAutoCompleteAdapter(context: Context, val requestManager: RequestMa
|
|||
val indices = this.indices!!
|
||||
when (cursor.getString(indices.type)) {
|
||||
Suggestions.AutoComplete.TYPE_HASHTAGS -> {
|
||||
if (account?.type == AccountType.FANFOU) {
|
||||
return "#${cursor.getString(indices.value)}#"
|
||||
}
|
||||
return "#${cursor.getString(indices.value)}"
|
||||
}
|
||||
Suggestions.AutoComplete.TYPE_USERS -> {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.extension
|
||||
|
||||
import android.net.Uri
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
fun HttpUrl.toUri() : Uri {
|
||||
return Uri.parse(toString())
|
||||
}
|
|
@ -2,6 +2,7 @@ package org.mariotaku.twidere.extension.model
|
|||
|
||||
import org.mariotaku.ktextension.addAllTo
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.util.UriUtils
|
||||
|
||||
|
||||
val ParcelableStatus.originalId: String
|
||||
|
@ -70,6 +71,23 @@ fun ParcelableStatus.toSummaryLine(): ParcelableActivity.SummaryLine {
|
|||
return result
|
||||
}
|
||||
|
||||
fun ParcelableStatus.extractFanfouHashtags(): List<String> {
|
||||
return spans?.filter { span ->
|
||||
var link = span.link
|
||||
if (link.startsWith("/")) {
|
||||
link = "http://fanfou.com$link"
|
||||
}
|
||||
if (UriUtils.getAuthority(link) != "fanfou.com") {
|
||||
return@filter false
|
||||
}
|
||||
if (span.start <= 0 || span.end > text_unescaped.lastIndex) return@filter false
|
||||
if (text_unescaped[span.start - 1] == '#' && text_unescaped[span.end] == '#') {
|
||||
return@filter true
|
||||
}
|
||||
return@filter false
|
||||
}?.map { text_unescaped.substring(it.start, it.end) }.orEmpty()
|
||||
}
|
||||
|
||||
private fun parcelableUserMention(key: UserKey, name: String, screenName: String) = ParcelableUserMention().also {
|
||||
it.key = key
|
||||
it.name = name
|
||||
|
|
|
@ -23,4 +23,4 @@ import org.mariotaku.twidere.model.AccountDetails
|
|||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
|
||||
data class GetTimelineResult<out T>(val account: AccountDetails, val data: List<T>,
|
||||
val users: Collection<ParcelableUser>)
|
||||
val users: Collection<ParcelableUser>, val hashtags: Collection<String>)
|
|
@ -3,6 +3,8 @@ package org.mariotaku.twidere.task.cache
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.support.v4.util.ArraySet
|
||||
import org.mariotaku.ktextension.ContentValues
|
||||
import org.mariotaku.ktextension.set
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.extension.bulkInsert
|
||||
import org.mariotaku.twidere.extension.model.applyTo
|
||||
|
@ -10,26 +12,30 @@ import org.mariotaku.twidere.extension.model.relationship
|
|||
import org.mariotaku.twidere.extension.queryAll
|
||||
import org.mariotaku.twidere.model.ParcelableRelationship
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers
|
||||
import org.mariotaku.twidere.model.task.GetTimelineResult
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.task.BaseAbstractTask
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
|
||||
class CacheUserRelationshipTask(
|
||||
class CacheTimelineResultTask(
|
||||
context: Context,
|
||||
val accountKey: UserKey,
|
||||
val accountType: String,
|
||||
val users: Collection<ParcelableUser>,
|
||||
val result: GetTimelineResult<*>,
|
||||
val cacheRelationship: Boolean
|
||||
) : BaseAbstractTask<Any?, Unit, Any?>(context) {
|
||||
|
||||
override fun doLongOperation(param: Any?) {
|
||||
val cr = context.contentResolver
|
||||
cr.bulkInsert(CachedUsers.CONTENT_URI, users, ParcelableUser::class.java)
|
||||
val account = result.account
|
||||
val users = result.users
|
||||
val hashtags = result.hashtags
|
||||
|
||||
cr.bulkInsert(CachedUsers.CONTENT_URI, users, ParcelableUser::class.java)
|
||||
ContentResolverUtils.bulkInsert(cr, CachedHashtags.CONTENT_URI, hashtags.map {
|
||||
ContentValues { this[CachedHashtags.NAME] = it.substringAfter("#") }
|
||||
})
|
||||
|
||||
if (cacheRelationship) {
|
||||
val selectionArgsList = users.mapTo(mutableListOf(accountKey.toString())) {
|
||||
val selectionArgsList = users.mapTo(mutableListOf(account.key.toString())) {
|
||||
it.key.toString()
|
||||
}
|
||||
@SuppressLint("Recycle")
|
|
@ -34,6 +34,7 @@ import org.mariotaku.twidere.annotation.ReadPositionTag
|
|||
import org.mariotaku.twidere.extension.api.batchGetRelationships
|
||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.extractFanfouHashtags
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.fragment.InteractionsTimelineFragment
|
||||
|
@ -84,17 +85,29 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
|||
}
|
||||
return GetTimelineResult(account, activities, activities.flatMap {
|
||||
it.sources?.toList().orEmpty()
|
||||
}, notifications.flatMapTo(HashSet()) { notification ->
|
||||
notification.status?.tags?.map { it.name }.orEmpty()
|
||||
})
|
||||
}
|
||||
AccountType.TWITTER -> {
|
||||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
if (account.isOfficial(context)) {
|
||||
val activities = microBlog.getActivitiesAboutMe(paging).map {
|
||||
val timeline = microBlog.getActivitiesAboutMe(paging)
|
||||
val activities = timeline.map {
|
||||
it.toParcelable(account, profileImageSize = profileImageSize)
|
||||
}
|
||||
|
||||
return GetTimelineResult(account, activities, activities.flatMap {
|
||||
it.sources?.toList().orEmpty()
|
||||
}, timeline.flatMapTo(HashSet()) { activity ->
|
||||
val mapResult = mutableSetOf<String>()
|
||||
activity.targetStatuses?.flatMapTo(mapResult) { status ->
|
||||
status.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
}
|
||||
activity.targetObjectStatuses?.flatMapTo(mapResult) { status ->
|
||||
status.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
}
|
||||
return@flatMapTo mapResult
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -106,16 +119,19 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
|||
}
|
||||
return GetTimelineResult(account, activities, activities.flatMap {
|
||||
it.sources?.toList().orEmpty()
|
||||
})
|
||||
}, activities.flatMap { it.extractFanfouHashtags() })
|
||||
}
|
||||
}
|
||||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
val activities = microBlog.getMentionsTimeline(paging).map {
|
||||
val timeline = microBlog.getMentionsTimeline(paging)
|
||||
val activities = timeline.map {
|
||||
InternalActivityCreator.status(it, account.key.id).toParcelable(account,
|
||||
profileImageSize = profileImageSize)
|
||||
}
|
||||
return GetTimelineResult(account, activities, activities.flatMap {
|
||||
it.sources?.toList().orEmpty()
|
||||
}, timeline.flatMap {
|
||||
it.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ abstract class GetActivitiesTask(
|
|||
context.contentResolver.notifyChange(contentUri, null)
|
||||
val exception = results.firstOrNull { it.second != null }?.second
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, false, exception))
|
||||
GetStatusesTask.cacheUserRelationship(context, results)
|
||||
GetStatusesTask.cacheItems(context, results)
|
||||
handler?.invoke(true)
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.mariotaku.twidere.annotation.AccountType
|
|||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.extractFanfouHashtags
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.fragment.HomeTimelineFragment
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
@ -66,14 +67,22 @@ class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) {
|
|||
val mapResult = mutableListOf(status.account.toParcelable(account))
|
||||
status.reblog?.account?.toParcelable(account)?.addTo(mapResult)
|
||||
return@flatMap mapResult
|
||||
}, timeline.flatMap { status ->
|
||||
status.tags?.map { it.name }.orEmpty()
|
||||
})
|
||||
}
|
||||
else -> {
|
||||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
val timeline = microBlog.getHomeTimeline(paging)
|
||||
return GetTimelineResult(account, timeline.map {
|
||||
val statuses = timeline.map {
|
||||
it.toParcelable(account, profileImageSize)
|
||||
}, timeline.flatMap { status ->
|
||||
}
|
||||
val hashtags = if (account.type == AccountType.FANFOU) statuses.flatMap { status ->
|
||||
return@flatMap status.extractFanfouHashtags()
|
||||
} else timeline.flatMap { status ->
|
||||
status.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
}
|
||||
return GetTimelineResult(account, statuses, timeline.flatMap { status ->
|
||||
val mapResult = mutableListOf(status.user.toParcelable(account,
|
||||
profileImageSize = profileImageSize))
|
||||
status.retweetedStatus?.user?.toParcelable(account,
|
||||
|
@ -81,7 +90,7 @@ class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) {
|
|||
status.quotedStatus?.user?.toParcelable(account,
|
||||
profileImageSize = profileImageSize)?.addTo(mapResult)
|
||||
return@flatMap mapResult
|
||||
})
|
||||
}, hashtags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.mariotaku.twidere.model.util.AccountUtils
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.task.BaseAbstractTask
|
||||
import org.mariotaku.twidere.task.cache.CacheUserRelationshipTask
|
||||
import org.mariotaku.twidere.task.cache.CacheTimelineResultTask
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.DebugLog
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||
|
@ -115,7 +115,7 @@ abstract class GetStatusesTask(
|
|||
context.contentResolver.notifyChange(contentUri, null)
|
||||
val exception = results.firstOrNull { it.second != null }?.second
|
||||
bus.post(GetStatusesTaskEvent(contentUri, false, exception))
|
||||
cacheUserRelationship(context, results)
|
||||
cacheItems(context, results)
|
||||
handler?.invoke(true)
|
||||
}
|
||||
|
||||
|
@ -234,11 +234,11 @@ abstract class GetStatusesTask(
|
|||
return timestamp + (sortId - lastSortId) * (499 - count) / sortDiff + extraValue.toLong()
|
||||
}
|
||||
|
||||
fun cacheUserRelationship(context: Context, results: List<Pair<GetTimelineResult<*>?, Exception?>>) {
|
||||
fun cacheItems(context: Context, results: List<Pair<GetTimelineResult<*>?, Exception?>>) {
|
||||
results.forEach { (result, _) ->
|
||||
if (result == null) return@forEach
|
||||
val account = result.account
|
||||
val task = CacheUserRelationshipTask(context, account.key, account.type, result.users,
|
||||
val task = CacheTimelineResultTask(context, result,
|
||||
account.type == AccountType.STATUSNET || account.isOfficial(context))
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.mariotaku.kpreferences.get
|
|||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_URI
|
||||
import org.mariotaku.twidere.constant.phishingLinksWaringKey
|
||||
import org.mariotaku.twidere.fragment.PhishingLinkWarningDialogFragment
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
||||
class DirectMessageOnLinkClickHandler(
|
||||
context: Context,
|
||||
|
@ -38,10 +39,10 @@ class DirectMessageOnLinkClickHandler(
|
|||
override val isPrivateData: Boolean
|
||||
get() = true
|
||||
|
||||
override fun openLink(link: String) {
|
||||
override fun openLink(accountKey: UserKey?, link: String) {
|
||||
if (manager != null && manager.isActive) return
|
||||
if (!hasShortenedLinks(link)) {
|
||||
super.openLink(link)
|
||||
super.openLink(accountKey, link)
|
||||
return
|
||||
}
|
||||
if (context is FragmentActivity && preferences[phishingLinksWaringKey]) {
|
||||
|
@ -52,7 +53,7 @@ class DirectMessageOnLinkClickHandler(
|
|||
fragment.arguments = args
|
||||
fragment.show(fm, "phishing_link_warning")
|
||||
} else {
|
||||
super.openLink(link)
|
||||
super.openLink(accountKey, link)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.text.SpannableStringBuilder
|
|||
import android.text.Spanned
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.URLSpan
|
||||
import okhttp3.HttpUrl
|
||||
import org.attoparser.ParseException
|
||||
import org.attoparser.config.ParseConfiguration
|
||||
import org.attoparser.simple.AbstractSimpleMarkupHandler
|
||||
|
@ -76,10 +75,7 @@ object HtmlSpanBuilder {
|
|||
private fun createSpan(info: TagInfo): Any? {
|
||||
when (info.nameLower) {
|
||||
"a" -> {
|
||||
var href = info.getAttribute("href") ?: return null
|
||||
if (HttpUrl.parse(href)?.scheme() == null) {
|
||||
href = "https://" + href
|
||||
}
|
||||
val href = info.getAttribute("href") ?: return null
|
||||
return URLSpan(href)
|
||||
}
|
||||
"b", "strong" -> {
|
||||
|
|
|
@ -26,14 +26,18 @@ import android.content.SharedPreferences
|
|||
import android.net.Uri
|
||||
import android.os.BadParcelableException
|
||||
import android.support.v4.content.ContextCompat
|
||||
import okhttp3.HttpUrl
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.TwidereConstants.USER_TYPE_TWITTER_COM
|
||||
import org.mariotaku.twidere.activity.WebLinkHandlerActivity
|
||||
import org.mariotaku.twidere.annotation.Referral
|
||||
import org.mariotaku.twidere.app.TwidereApplication
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_HOST
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
|
||||
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
|
||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||
import org.mariotaku.twidere.extension.model.AcctPlaceholderUserKey
|
||||
import org.mariotaku.twidere.extension.toUri
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
|
||||
|
@ -65,7 +69,7 @@ open class OnLinkClickHandler(
|
|||
if (accountKey != null && isMedia(link, extraId)) {
|
||||
openMedia(accountKey, extraId, sensitive, link, start, end)
|
||||
} else {
|
||||
openLink(link)
|
||||
openLink(accountKey, link)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -74,20 +78,15 @@ open class OnLinkClickHandler(
|
|||
openMedia(accountKey, extraId, sensitive, link, start, end)
|
||||
} else {
|
||||
val authority = UriUtils.getAuthority(link)
|
||||
if (authority == null) {
|
||||
openLink(link)
|
||||
if (authority == "fanfou.com") {
|
||||
if (accountKey != null && handleFanfouLink(link, orig, accountKey)) {
|
||||
return true
|
||||
}
|
||||
when (authority) {
|
||||
"fanfou.com" -> if (accountKey != null && handleFanfouLink(link, orig, accountKey)) {
|
||||
} else if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) {
|
||||
openTwitterLink(accountKey, link)
|
||||
return true
|
||||
}
|
||||
else -> if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) {
|
||||
openTwitterLink(link, accountKey!!)
|
||||
return true
|
||||
}
|
||||
}
|
||||
openLink(link)
|
||||
openLink(accountKey, link)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -132,17 +131,27 @@ open class OnLinkClickHandler(
|
|||
preferences[displaySensitiveContentsKey])
|
||||
}
|
||||
|
||||
protected open fun openLink(link: String) {
|
||||
protected open fun openLink(accountKey: UserKey?, link: String) {
|
||||
if (manager != null && manager.isActive) return
|
||||
openLink(context, preferences, Uri.parse(link))
|
||||
val uri = Uri.parse(link)
|
||||
if (uri.isRelative && accountKey != null && accountKey.host != null) {
|
||||
val absUri = HttpUrl.parse("http://${accountKey.host}/").resolve(link).toUri()
|
||||
openLink(context, preferences, absUri)
|
||||
return
|
||||
}
|
||||
openLink(context, preferences, uri)
|
||||
}
|
||||
|
||||
protected fun openTwitterLink(link: String, accountKey: UserKey) {
|
||||
protected fun openTwitterLink(accountKey: UserKey?, link: String) {
|
||||
if (manager != null && manager.isActive) return
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.setClass(context, WebLinkHandlerActivity::class.java)
|
||||
if (accountKey != null) {
|
||||
intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey)
|
||||
} else {
|
||||
intent.putExtra(EXTRA_ACCOUNT_HOST, USER_TYPE_TWITTER_COM)
|
||||
}
|
||||
intent.setExtrasClassLoader(TwidereApplication::class.java.classLoader)
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
try {
|
||||
|
@ -199,3 +208,4 @@ open class OnLinkClickHandler(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class StatusAdapterLinkClickHandler<D>(context: Context, preferences: SharedPref
|
|||
val media = ParcelableMediaUtils.getAllMedia(status)
|
||||
val current = StatusLinkClickHandler.findByLink(media, link)
|
||||
if (current != null && current.open_browser) {
|
||||
openLink(link)
|
||||
openLink(accountKey, link)
|
||||
} else {
|
||||
IntentUtils.openMedia(context, status, current, preferences[newDocumentApiKey],
|
||||
preferences[displaySensitiveContentsKey])
|
||||
|
|
|
@ -45,7 +45,7 @@ open class StatusLinkClickHandler(
|
|||
val status = status
|
||||
val current = findByLink(status!!.media, link)
|
||||
if (current == null || current.open_browser) {
|
||||
openLink(link)
|
||||
openLink(accountKey, link)
|
||||
} else {
|
||||
IntentUtils.openMedia(context, status, current, preferences[newDocumentApiKey],
|
||||
preferences[displaySensitiveContentsKey])
|
||||
|
|
Loading…
Reference in New Issue