move bottom sheet from fragments to activities (#628)
* move bottom sheet from fragments to activities * move BottomSheetLogic to dedicated abstract Activity * change tests * improve codemain
parent
bf91d050d2
commit
8db482bb46
@ -0,0 +1,196 @@ |
||||
/* 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 |
||||
|
||||
/** 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 |
||||
|
||||
abstract fun getMastodonApi(): 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 = getMastodonApi().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) |
||||
startActivity(intent) |
||||
} |
||||
} |
||||
|
||||
open fun viewAccount(id: String) { |
||||
val intent = Intent(this, AccountActivity::class.java) |
||||
intent.putExtra("id", id) |
||||
startActivity(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()) |
||||
} |
@ -1,27 +1,22 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:id="@+id/activity_view_thread" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
tools:context="com.keylesspalace.tusky.ModalTimelineActivity"> |
||||
|
||||
<include layout="@layout/toolbar_basic" /> |
||||
|
||||
<include |
||||
layout="@layout/toolbar_shadow_shim" |
||||
android:layout_width="0dp" |
||||
android:layout_height="4dp" |
||||
app:layout_constraintLeft_toLeftOf="parent" |
||||
app:layout_constraintRight_toRightOf="parent" |
||||
app:layout_constraintTop_toBottomOf="@id/appbar" /> |
||||
|
||||
<FrameLayout |
||||
android:id="@+id/content_frame" |
||||
android:layout_width="0dp" |
||||
android:layout_height="0dp" |
||||
app:layout_constraintTop_toBottomOf="@id/toolbar_shadow_shim" |
||||
app:layout_constraintLeft_toLeftOf="parent" |
||||
app:layout_constraintRight_toRightOf="parent" |
||||
app:layout_constraintBottom_toBottomOf="parent"/> |
||||
</android.support.constraint.ConstraintLayout> |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
||||
|
||||
<include layout="@layout/toolbar_shadow_shim" /> |
||||
|
||||
<include layout="@layout/item_status_bottom_sheet"/> |
||||
|
||||
</android.support.design.widget.CoordinatorLayout> |
||||
|
@ -1,19 +1,12 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.design.widget.CoordinatorLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_width="match_parent"> |
||||
<android.support.v4.widget.SwipeRefreshLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_gravity="top"> |
||||
android:layout_gravity="top"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
</android.support.v4.widget.SwipeRefreshLayout> |
||||
<include layout="@layout/item_status_bottom_sheet"/> |
||||
</android.support.design.widget.CoordinatorLayout> |
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
</android.support.v4.widget.SwipeRefreshLayout> |
@ -1,19 +1,13 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.design.widget.CoordinatorLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_width="match_parent"> |
||||
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_gravity="top"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_gravity="top"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:scrollbars="vertical" /> |
||||
</android.support.v4.widget.SwipeRefreshLayout> |
||||
<include layout="@layout/item_status_bottom_sheet" /> |
||||
</android.support.design.widget.CoordinatorLayout> |
||||
android:scrollbars="vertical" /> |
||||
</android.support.v4.widget.SwipeRefreshLayout> |
||||
|
Loading…
Reference in new issue