chinwag-android/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt
2018-07-31 21:25:25 +02:00

197 lines
6.5 KiB
Kotlin

/* 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
import javax.inject.Inject
/** 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
@Inject
lateinit var mastodonApi: MastodonApi
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
}
val call = mastodonApi.search(url, true)
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)
startActivityWithSlideInAnimation(intent)
}
}
open fun viewAccount(id: String) {
val intent = AccountActivity.getIntent(this, id)
startActivityWithSlideInAnimation(intent)
}
@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())
}