2018-05-06 22:05:54 +02:00
|
|
|
/* Copyright 2018 Conny Duck
|
|
|
|
*
|
|
|
|
* This file is a part of Tusky.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Tusky 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 Tusky; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
|
|
|
|
package com.keylesspalace.tusky
|
|
|
|
|
|
|
|
import android.content.Intent
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.support.annotation.VisibleForTesting
|
|
|
|
import android.support.design.widget.BottomSheetBehavior
|
|
|
|
import android.view.View
|
|
|
|
import android.widget.LinearLayout
|
|
|
|
import com.keylesspalace.tusky.entity.SearchResults
|
|
|
|
import com.keylesspalace.tusky.entity.Status
|
|
|
|
import com.keylesspalace.tusky.network.MastodonApi
|
|
|
|
import com.keylesspalace.tusky.util.LinkHelper
|
|
|
|
import retrofit2.Call
|
|
|
|
import retrofit2.Callback
|
|
|
|
import retrofit2.Response
|
|
|
|
import java.net.URI
|
|
|
|
import java.net.URISyntaxException
|
2018-05-08 19:15:10 +02:00
|
|
|
import javax.inject.Inject
|
2018-05-06 22:05:54 +02:00
|
|
|
|
|
|
|
/** this is the base class for all activities that open links
|
|
|
|
* links are checked against the api if they are mastodon links so they can be openend in Tusky
|
|
|
|
* Subclasses must have a bottom sheet with Id item_status_bottom_sheet in their layout hierachy
|
|
|
|
*/
|
|
|
|
|
|
|
|
abstract class BottomSheetActivity : BaseActivity() {
|
|
|
|
|
|
|
|
lateinit var bottomSheet: BottomSheetBehavior<LinearLayout>
|
|
|
|
var searchUrl: String? = null
|
|
|
|
|
2018-05-08 19:15:10 +02:00
|
|
|
@Inject
|
|
|
|
lateinit var mastodonApi: MastodonApi
|
2018-05-06 22:05:54 +02:00
|
|
|
|
|
|
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onPostCreate(savedInstanceState)
|
|
|
|
|
|
|
|
val bottomSheetLayout: LinearLayout = findViewById(R.id.item_status_bottom_sheet)
|
|
|
|
bottomSheet = BottomSheetBehavior.from(bottomSheetLayout)
|
|
|
|
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
|
|
|
bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
|
|
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
|
|
|
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
|
|
cancelActiveSearch()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun viewUrl(url: String) {
|
|
|
|
if (!looksLikeMastodonUrl(url)) {
|
|
|
|
openLink(url)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-08 19:15:10 +02:00
|
|
|
val call = mastodonApi.search(url, true)
|
2018-05-06 22:05:54 +02:00
|
|
|
call.enqueue(object : Callback<SearchResults> {
|
|
|
|
override fun onResponse(call: Call<SearchResults>, response: Response<SearchResults>) {
|
|
|
|
if (getCancelSearchRequested(url)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
onEndSearch(url)
|
|
|
|
if (response.isSuccessful) {
|
|
|
|
// According to the mastodon API doc, if the search query is a url,
|
|
|
|
// only exact matches for statuses or accounts are returned
|
|
|
|
// which is good, because pleroma returns a different url
|
|
|
|
// than the public post link
|
|
|
|
val searchResult = response.body()
|
|
|
|
if(searchResult != null) {
|
|
|
|
if (searchResult.statuses.isNotEmpty()) {
|
|
|
|
viewThread(searchResult.statuses[0])
|
|
|
|
return
|
|
|
|
} else if (searchResult.accounts.isNotEmpty()) {
|
|
|
|
viewAccount(searchResult.accounts[0].id)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
openLink(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onFailure(call: Call<SearchResults>, t: Throwable) {
|
|
|
|
if (!getCancelSearchRequested(url)) {
|
|
|
|
onEndSearch(url)
|
|
|
|
openLink(url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
callList.add(call)
|
|
|
|
onBeginSearch(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun viewThread(status: Status) {
|
|
|
|
if (!isSearching()) {
|
|
|
|
val intent = Intent(this, ViewThreadActivity::class.java)
|
|
|
|
intent.putExtra("id", status.actionableId)
|
|
|
|
intent.putExtra("url", status.actionableStatus.url)
|
2018-07-31 21:25:25 +02:00
|
|
|
startActivityWithSlideInAnimation(intent)
|
2018-05-06 22:05:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun viewAccount(id: String) {
|
2018-06-18 13:26:18 +02:00
|
|
|
val intent = AccountActivity.getIntent(this, id)
|
2018-07-31 21:25:25 +02:00
|
|
|
startActivityWithSlideInAnimation(intent)
|
2018-05-06 22:05:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
fun onBeginSearch(url: String) {
|
|
|
|
searchUrl = url
|
|
|
|
showQuerySheet()
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
fun getCancelSearchRequested(url: String): Boolean {
|
|
|
|
return url != searchUrl
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
fun isSearching(): Boolean {
|
|
|
|
return searchUrl != null
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
fun onEndSearch(url: String?) {
|
|
|
|
if (url == searchUrl) {
|
|
|
|
// Don't clear query if there's no match,
|
|
|
|
// since we might just now be getting the response for a canceled search
|
|
|
|
searchUrl = null
|
|
|
|
hideQuerySheet()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
fun cancelActiveSearch() {
|
|
|
|
if (isSearching()) {
|
|
|
|
onEndSearch(searchUrl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
open fun openLink(url: String) {
|
|
|
|
LinkHelper.openLink(url, this)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun showQuerySheet() {
|
|
|
|
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun hideQuerySheet() {
|
|
|
|
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://mastodon.foo.bar/@User
|
|
|
|
// https://mastodon.foo.bar/@User/43456787654678
|
|
|
|
// https://pleroma.foo.bar/users/User
|
|
|
|
// https://pleroma.foo.bar/users/43456787654678
|
|
|
|
// https://pleroma.foo.bar/notice/43456787654678
|
|
|
|
// https://pleroma.foo.bar/objects/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
|
|
|
fun looksLikeMastodonUrl(urlString: String): Boolean {
|
|
|
|
val uri: URI
|
|
|
|
try {
|
|
|
|
uri = URI(urlString)
|
|
|
|
} catch (e: URISyntaxException) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uri.query != null ||
|
|
|
|
uri.fragment != null ||
|
|
|
|
uri.path == null) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
val path = uri.path
|
|
|
|
return path.matches("^/@[^/]+$".toRegex()) ||
|
|
|
|
path.matches("^/users/[^/]+$".toRegex()) ||
|
|
|
|
path.matches("^/@[^/]+/\\d+$".toRegex()) ||
|
|
|
|
path.matches("^/notice/\\d+$".toRegex()) ||
|
|
|
|
path.matches("^/objects/[-a-f0-9]+$".toRegex())
|
|
|
|
}
|