1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

mastodon improvement

fixed link handler crashes
updated version
This commit is contained in:
Mariotaku Lee 2017-04-23 02:11:35 +08:00
parent 3c7de6f284
commit 9a6f9e09f6
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
25 changed files with 278 additions and 86 deletions

View File

@ -81,7 +81,7 @@ public interface AccountsResources {
Relationship unmuteUser(@Path("id") String id) throws MicroBlogException;
@GET("/v1/accounts/relationships")
LinkHeaderList<Relationship> getRelationships(@Query(value = "id", arrayDelimiter = ',')
LinkHeaderList<Relationship> getRelationships(@Query(value = "id[]")
String[] id) throws MicroBlogException;
@GET("/v1/accounts/search")

View File

@ -18,6 +18,7 @@
package org.mariotaku.microblog.library.mastodon.api;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.mastodon.model.Account;
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderList;
import org.mariotaku.microblog.library.twitter.model.Paging;
@ -30,5 +31,5 @@ import org.mariotaku.restfu.annotation.param.Query;
public interface BlocksResources {
@GET("/v1/blocks")
LinkHeaderList<Account> getBlocks(@Query Paging paging);
LinkHeaderList<Account> getBlocks(@Query Paging paging) throws MicroBlogException;
}

View File

@ -18,6 +18,7 @@
package org.mariotaku.microblog.library.mastodon.api;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.mastodon.model.Account;
import org.mariotaku.restfu.annotation.method.POST;
import org.mariotaku.restfu.annotation.param.Param;
@ -35,5 +36,5 @@ public interface FollowsResources {
* @return The local representation of the followed account, as an {@link Account}.
*/
@POST("/v1/follows")
Account followRemoteUser(@Param("uri") String uri);
Account followRemoteUser(@Param("uri") String uri) throws MicroBlogException;
}

View File

@ -18,6 +18,7 @@
package org.mariotaku.microblog.library.mastodon.api;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.mastodon.model.Account;
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderList;
import org.mariotaku.microblog.library.twitter.model.Paging;
@ -30,5 +31,5 @@ import org.mariotaku.restfu.annotation.param.Query;
public interface MutesResources {
@GET("/v1/mutes")
LinkHeaderList<Account> getMutes(@Query Paging paging);
LinkHeaderList<Account> getMutes(@Query Paging paging) throws MicroBlogException;
}

View File

@ -18,9 +18,20 @@
package org.mariotaku.microblog.library.mastodon.api;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.mastodon.model.Results;
import org.mariotaku.microblog.library.twitter.model.Paging;
import org.mariotaku.restfu.annotation.method.GET;
import org.mariotaku.restfu.annotation.param.Query;
/**
* Created by mariotaku on 2017/4/17.
*/
public interface SearchResources {
@GET("/v1/search")
Results search(@Query("q") String query, @Query("resolve") boolean resolve,
@Query Paging paging) throws MicroBlogException;
}

View File

@ -21,7 +21,7 @@ package org.mariotaku.microblog.library.mastodon.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import java.util.Arrays;
import java.util.List;
/**
* {@see https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#context}
@ -34,26 +34,26 @@ public class Context {
* The ancestors of the status in the conversation, as a list of {@link Status}
*/
@JsonField(name = "ancestors")
Status[] ancestors;
List<Status> ancestors;
/**
* The descendants of the status in the conversation, as a list of {@link Status}
*/
@JsonField(name = "descendants")
Status[] descendants;
List<Status> descendants;
public Status[] getAncestors() {
public List<Status> getAncestors() {
return ancestors;
}
public Status[] getDescendants() {
public List<Status> getDescendants() {
return descendants;
}
@Override
public String toString() {
return "Context{" +
"ancestors=" + Arrays.toString(ancestors) +
", descendants=" + Arrays.toString(descendants) +
"ancestors=" + ancestors +
", descendants=" + descendants +
'}';
}
}

View File

@ -20,22 +20,20 @@ package org.mariotaku.microblog.library.mastodon.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.mariotaku.restfu.http.HttpResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Created by mariotaku on 2017/4/21.
*/
public class LinkHeaderList<E> extends ArrayList<E> {
public class LinkHeaderList<E> extends ArrayList<E> implements LinkHeaderResponse {
@NonNull
private Map<String, String> linkParts = new HashMap<>();
@Nullable
private Map<String, String> linkParts;
public LinkHeaderList(int initialCapacity) {
super(initialCapacity);
@ -48,33 +46,15 @@ public class LinkHeaderList<E> extends ArrayList<E> {
super(c);
}
public final void processResponseHeader(HttpResponse resp) {
linkParts.clear();
String linkHeader = resp.getHeader("Link");
if (linkHeader == null) return;
for (String link : TextUtils.split(linkHeader, ",")) {
String[] segments = TextUtils.split(link, ";");
if (segments.length < 2) continue;
String linkPart = segments[0].trim();
if (!linkPart.startsWith("<") || !linkPart.endsWith(">"))
continue;
linkPart = linkPart.substring(1, linkPart.length() - 1);
for (int i = 1; i < segments.length; i++) {
String[] rel = TextUtils.split(segments[i].trim(), "=");
if (rel.length < 2 || !"rel".equals(rel[0]))
continue;
String relValue = rel[1];
if (relValue.startsWith("\"") && relValue.endsWith("\""))
relValue = relValue.substring(1, relValue.length() - 1);
linkParts.put(relValue, linkPart);
}
}
@Override
public final void processResponseHeader(@NonNull HttpResponse resp) {
linkParts = Parser.parse(resp);
}
@Nullable
@Override
public String getLinkPart(String key) {
if (linkParts == null) return null;
return linkParts.get(key);
}
}

View File

@ -0,0 +1,70 @@
/*
* Twidere - Twitter client for Android
*
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.microblog.library.mastodon.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.mariotaku.restfu.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Created by mariotaku on 2017/4/23.
*/
public interface LinkHeaderResponse {
void processResponseHeader(@NonNull HttpResponse resp);
@Nullable
String getLinkPart(String key);
class Parser {
@Nullable
public static Map<String, String> parse(@NonNull HttpResponse resp) {
String linkHeader = resp.getHeader("Link");
if (linkHeader == null) return null;
Map<String, String> linkParts = new HashMap<>();
for (String link : TextUtils.split(linkHeader, ",")) {
String[] segments = TextUtils.split(link, ";");
if (segments.length < 2) continue;
String linkPart = segments[0].trim();
if (!linkPart.startsWith("<") || !linkPart.endsWith(">"))
continue;
linkPart = linkPart.substring(1, linkPart.length() - 1);
for (int i = 1; i < segments.length; i++) {
String[] rel = TextUtils.split(segments[i].trim(), "=");
if (rel.length < 2 || !"rel".equals(rel[0]))
continue;
String relValue = rel[1];
if (relValue.startsWith("\"") && relValue.endsWith("\""))
relValue = relValue.substring(1, relValue.length() - 1);
linkParts.put(relValue, linkPart);
}
}
return linkParts;
}
}
}

View File

@ -18,10 +18,16 @@
package org.mariotaku.microblog.library.mastodon.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import java.util.Arrays;
import org.mariotaku.restfu.http.HttpResponse;
import java.util.List;
import java.util.Map;
/**
* {@see https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#results}
@ -29,41 +35,56 @@ import java.util.Arrays;
* Created by mariotaku on 2017/4/17.
*/
@JsonObject
public class Results {
public class Results implements LinkHeaderResponse {
/**
* An array of matched {@link Account}
* A list of matched {@link Account}
*/
@JsonField(name = "accounts")
Account[] accounts;
List<Account> accounts;
/**
* An array of matched {@link Status}
* A list of matched {@link Status}
*/
@JsonField(name = "statuses")
Status[] statuses;
List<Status> statuses;
/**
* An array of matched hashtags, as strings
* A list of matched hashtags, as strings
*/
@JsonField(name = "hashtags")
String[] hashtags;
List<String> hashtags;
public Account[] getAccounts() {
@Nullable
private Map<String, String> linkParts;
public List<Account> getAccounts() {
return accounts;
}
public Status[] getStatuses() {
public List<Status> getStatuses() {
return statuses;
}
public String[] getHashtags() {
public List<String> getHashtags() {
return hashtags;
}
@Override
public final void processResponseHeader(@NonNull HttpResponse resp) {
linkParts = Parser.parse(resp);
}
@Nullable
@Override
public String getLinkPart(String key) {
if (linkParts == null) return null;
return linkParts.get(key);
}
@Override
public String toString() {
return "Results{" +
"accounts=" + Arrays.toString(accounts) +
", statuses=" + Arrays.toString(statuses) +
", hashtags=" + Arrays.toString(hashtags) +
"accounts=" + accounts +
", statuses=" + statuses +
", hashtags=" + hashtags +
'}';
}
}

View File

@ -93,6 +93,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
String AUTHORITY_USER_BLOCKS = "user_blocks";
String AUTHORITY_STATUS = "status";
String AUTHORITY_PUBLIC_TIMELINE = "public_timeline";
String AUTHORITY_NETWORK_PUBLIC_TIMELINE = "network_public_timeline";
String AUTHORITY_MESSAGES = "direct_messages";
String AUTHORITY_SEARCH = "search";
String AUTHORITY_MAP = "map";

View File

@ -102,7 +102,8 @@ public class TabArguments implements TwidereConstants {
case CustomTabType.NOTIFICATIONS_TIMELINE:
case CustomTabType.DIRECT_MESSAGES:
case CustomTabType.TRENDS_SUGGESTIONS:
case CustomTabType.PUBLIC_TIMELINE: {
case CustomTabType.PUBLIC_TIMELINE:
case CustomTabType.NETWORK_PUBLIC_TIMELINE: {
return LoganSquare.parse(json, TabArguments.class);
}
case CustomTabType.USER_TIMELINE:

View File

@ -36,8 +36,8 @@ android {
applicationId "org.mariotaku.twidere"
minSdkVersion project.properties['overrideMinSdkVersion'] ?: 14
targetSdkVersion 25
versionCode 340
versionName '3.5.23'
versionCode 341
versionName '3.5.24'
multiDexEnabled true
buildConfigField 'boolean', 'LEAK_CANARY_ENABLED', 'Boolean.parseBoolean("true")'

View File

@ -78,6 +78,7 @@ public interface Constants extends TwidereConstants {
int LINK_ID_INTERACTIONS = 35;
int LINK_ID_PUBLIC_TIMELINE = 36;
int LINK_ID_NETWORK_PUBLIC_TIMELINE = 37;
int LINK_ID_MUTES_USERS = 41;
int LINK_ID_MAP = 51;
int LINK_ID_ACCOUNTS = 101;

View File

@ -125,7 +125,8 @@ public class CustomTabUtils implements Constants {
case CustomTabType.NOTIFICATIONS_TIMELINE:
case CustomTabType.DIRECT_MESSAGES:
case CustomTabType.TRENDS_SUGGESTIONS:
case CustomTabType.PUBLIC_TIMELINE: {
case CustomTabType.PUBLIC_TIMELINE:
case CustomTabType.NETWORK_PUBLIC_TIMELINE: {
return new TabArguments();
}
case CustomTabType.USER_TIMELINE:

View File

@ -62,7 +62,6 @@ import org.mariotaku.twidere.fragment.message.MessageConversationInfoFragment
import org.mariotaku.twidere.fragment.message.MessageNewConversationFragment
import org.mariotaku.twidere.fragment.message.MessagesConversationFragment
import org.mariotaku.twidere.fragment.message.MessagesEntriesFragment
import org.mariotaku.twidere.fragment.status.*
import org.mariotaku.twidere.fragment.statuses.*
import org.mariotaku.twidere.fragment.users.*
import org.mariotaku.twidere.graphic.EmptyDrawable
@ -461,6 +460,9 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_PUBLIC_TIMELINE -> {
title = getString(R.string.title_public_timeline)
}
LINK_ID_NETWORK_PUBLIC_TIMELINE -> {
title = getString(R.string.title_network_public_timeline)
}
LINK_ID_FILTERS_IMPORT_BLOCKS -> {
title = getString(R.string.title_select_users)
}
@ -579,7 +581,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
fragment = UserFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramProfileUrl = uri.getQueryParameter(QUERY_PARAM_PROFILE_URL)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -598,7 +600,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_LIST_MEMBERSHIPS -> {
fragment = UserListMembershipsFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -610,7 +612,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
fragment = UserTimelineFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramProfileUrl = uri.getQueryParameter(QUERY_PARAM_PROFILE_URL)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -625,7 +627,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_MEDIA_TIMELINE -> {
fragment = UserMediaTimelineFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -637,7 +639,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_FAVORITES -> {
fragment = UserFavoritesFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -650,7 +652,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_FOLLOWERS -> {
fragment = UserFollowersFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -662,7 +664,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_FRIENDS -> {
fragment = UserFriendsFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -704,10 +706,13 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_PUBLIC_TIMELINE -> {
fragment = PublicTimelineFragment()
}
LINK_ID_NETWORK_PUBLIC_TIMELINE -> {
fragment = NetworkPublicTimelineFragment()
}
LINK_ID_USER_LIST -> {
fragment = UserListFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
val paramListId = uri.getQueryParameter(QUERY_PARAM_LIST_ID)
val paramListName = uri.getQueryParameter(QUERY_PARAM_LIST_NAME)
if ((TextUtils.isEmpty(paramListName) || TextUtils.isEmpty(paramScreenName) && paramUserKey == null) && TextUtils.isEmpty(paramListId)) {
@ -730,7 +735,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_LISTS -> {
fragment = ListsFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -742,7 +747,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_GROUPS -> {
fragment = UserGroupsFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
if (!args.containsKey(EXTRA_SCREEN_NAME)) {
args.putString(EXTRA_SCREEN_NAME, paramScreenName)
}
@ -754,7 +759,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_LIST_TIMELINE -> {
fragment = UserListTimelineFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
val paramListId = uri.getQueryParameter(QUERY_PARAM_LIST_ID)
val paramListName = uri.getQueryParameter(QUERY_PARAM_LIST_NAME)
if ((TextUtils.isEmpty(paramListName) || TextUtils.isEmpty(paramScreenName) && paramUserKey == null) && TextUtils.isEmpty(paramListId)) {
@ -768,7 +773,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_LIST_MEMBERS -> {
fragment = UserListMembersFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
val paramListId = uri.getQueryParameter(QUERY_PARAM_LIST_ID)
val paramListName = uri.getQueryParameter(QUERY_PARAM_LIST_NAME)
if ((TextUtils.isEmpty(paramListName) || TextUtils.isEmpty(paramScreenName) && paramUserKey == null) && TextUtils.isEmpty(paramListId))
@ -781,7 +786,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_USER_LIST_SUBSCRIBERS -> {
fragment = UserListSubscribersFragment()
val paramScreenName = uri.getQueryParameter(QUERY_PARAM_SCREEN_NAME)
val paramUserKey = Utils.getUserKeyParam(uri)?.let(UserKey::valueOf)
val paramUserKey = uri.getUserKeyQueryParameter()
val paramListId = uri.getQueryParameter(QUERY_PARAM_LIST_ID)
val paramListName = uri.getQueryParameter(QUERY_PARAM_LIST_NAME)
if (TextUtils.isEmpty(paramListId) && (TextUtils.isEmpty(paramListName) || TextUtils.isEmpty(paramScreenName) && paramUserKey == null))
@ -883,4 +888,9 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
}
interface HideUiOnScroll
private fun Uri.getUserKeyQueryParameter() : UserKey? {
val value = getQueryParameter(QUERY_PARAM_USER_KEY) ?: getQueryParameter(QUERY_PARAM_USER_ID)
return value?.let(UserKey::valueOf)
}
}

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.extension.model.api.mastodon
import android.net.Uri
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderList
import org.mariotaku.microblog.library.mastodon.model.LinkHeaderResponse
import org.mariotaku.twidere.model.pagination.PaginatedArrayList
import org.mariotaku.twidere.model.pagination.PaginatedList
import org.mariotaku.twidere.model.pagination.Pagination
@ -37,7 +38,7 @@ inline fun <T, R> LinkHeaderList<T>.mapToPaginated(transform: (T) -> R): Paginat
return result
}
fun LinkHeaderList<*>.getLinkPagination(key: String): Pagination? {
fun LinkHeaderResponse.getLinkPagination(key: String): Pagination? {
val uri = getLinkPart(key)?.let(Uri::parse) ?: return null
val maxId = uri.getQueryParameter("max_id")
val sinceId = uri.getQueryParameter("since_id")

View File

@ -0,0 +1,36 @@
/*
* 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.model.api.mastodon
import org.mariotaku.microblog.library.mastodon.model.Results
import org.mariotaku.twidere.model.pagination.PaginatedArrayList
import org.mariotaku.twidere.model.pagination.PaginatedList
/**
* Created by mariotaku on 2017/4/23.
*/
inline fun <T, R> Results.mapToPaginated(listSelector: (Results) -> List<T>?, transform: (T) -> R): PaginatedList<R> {
val list = listSelector(this) ?: return PaginatedArrayList()
val result = list.mapTo(PaginatedArrayList(list.size), transform)
result.previousPage = getLinkPagination("prev")
result.nextPage = getLinkPagination("next")
return result
}

View File

@ -337,6 +337,7 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
var hasDmTab = false
var hasInteractionsTab = false
var hasPublicTimelineTab = false
var hasNetworkPublicTimelineTab = false
for (tab in tabs) {
when (tab.type) {
CustomTabType.DIRECT_MESSAGES -> {
@ -354,6 +355,11 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
hasPublicTimelineTab = hasAccountInTab(tab, account.key, true)
}
}
CustomTabType.NETWORK_PUBLIC_TIMELINE -> {
if (!hasNetworkPublicTimelineTab) {
hasNetworkPublicTimelineTab = hasAccountInTab(tab, account.key, true)
}
}
}
}
val menu = navigationView.menu
@ -374,6 +380,7 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
var hasLists = false
var hasGroups = false
var hasPublicTimeline = false
var hasNetworkPublicTimeline = false
when (account.type) {
AccountType.TWITTER -> {
hasLists = true
@ -381,17 +388,20 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
AccountType.STATUSNET -> {
hasGroups = true
hasPublicTimeline = !hasPublicTimelineTab
hasNetworkPublicTimeline = !hasNetworkPublicTimelineTab
}
AccountType.FANFOU -> {
hasPublicTimeline = !hasPublicTimelineTab
}
AccountType.MASTODON -> {
hasPublicTimeline = !hasPublicTimelineTab
hasNetworkPublicTimeline = !hasNetworkPublicTimelineTab
}
}
menu.setItemAvailability(R.id.groups, hasGroups)
menu.setItemAvailability(R.id.lists, hasLists)
menu.setItemAvailability(R.id.public_timeline, hasPublicTimeline)
menu.setItemAvailability(R.id.network_public_timeline, hasNetworkPublicTimeline)
}
private fun hasAccountInTab(tab: SupportTabSpec, accountKey: UserKey, isActivated: Boolean): Boolean {
@ -573,6 +583,9 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
R.id.public_timeline -> {
IntentUtils.openPublicTimeline(activity, account.key)
}
R.id.network_public_timeline -> {
IntentUtils.openNetworkPublicTimeline(activity, account.key)
}
R.id.messages -> {
IntentUtils.openDirectMessages(activity, account.key)
}

View File

@ -38,7 +38,7 @@ class NetworkPublicTimelineFragment : ParcelableStatusesFragment() {
get() {
val accountKey = Utils.getAccountKey(context, arguments)
val result = ArrayList<String>()
result.add(AUTHORITY_PUBLIC_TIMELINE)
result.add(AUTHORITY_NETWORK_PUBLIC_TIMELINE)
result.add("account=$accountKey")
return result.toTypedArray()
}

View File

@ -25,15 +25,20 @@ import android.support.annotation.WorkerThread
import org.mariotaku.commons.parcel.ParcelUtils
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.SearchQuery
import org.mariotaku.microblog.library.twitter.model.Status
import org.mariotaku.twidere.alias.MastodonStatus
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.exception.APINotSupportedException
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
import org.mariotaku.twidere.extension.model.api.toParcelable
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.ParcelableStatus
import org.mariotaku.twidere.model.pagination.PaginatedArrayList
import org.mariotaku.twidere.model.pagination.PaginatedList
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
@ -57,11 +62,23 @@ class ConversationLoader(
@Throws(MicroBlogException::class)
override fun getStatuses(account: AccountDetails, paging: Paging): PaginatedList<ParcelableStatus> {
return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated {
it.toParcelable(account, profileImageSize)
when (account.type) {
AccountType.MASTODON -> return getMastodonStatuses(account, paging).mapTo(PaginatedArrayList()) {
it.toParcelable(account)
}
else -> return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated {
it.toParcelable(account, profileImageSize)
}
}
}
private fun getMastodonStatuses(account: AccountDetails, paging: Paging): List<MastodonStatus> {
val mastodon = account.newMicroBlogInstance(context, Mastodon::class.java)
canLoadAllReplies = true
val statusContext = mastodon.getStatusContext(status.id)
return statusContext.ancestors + statusContext.descendants
}
@Throws(MicroBlogException::class)
private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): List<Status> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
@ -85,10 +102,12 @@ class ConversationLoader(
canLoadAllReplies = true
return microBlog.getContextTimeline(status.id, paging)
}
else -> {
throw APINotSupportedException(account.type)
}
}
// Set to true because there's no conversation support on this platform
canLoadAllReplies = true
return showConversationCompat(microBlog, account, status, false)
return showConversationCompat(microBlog, account, status, true)
}
@Throws(MicroBlogException::class)

View File

@ -24,11 +24,15 @@ import android.database.sqlite.SQLiteDatabase
import android.support.annotation.WorkerThread
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.mastodon.model.Results
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.SearchQuery
import org.mariotaku.microblog.library.twitter.model.Status
import org.mariotaku.microblog.library.twitter.model.UniversalSearchQuery
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
import org.mariotaku.twidere.extension.model.api.toParcelable
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.official
@ -54,8 +58,13 @@ open class TweetSearchLoader(
@Throws(MicroBlogException::class)
override fun getStatuses(account: AccountDetails, paging: Paging): PaginatedList<ParcelableStatus> {
return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated {
it.toParcelable(account, profileImageSize)
when (account.type) {
AccountType.MASTODON -> return getMastodonStatuses(account, paging).mapToPaginated(Results::getStatuses) {
it.toParcelable(account)
}
else -> return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated {
it.toParcelable(account, profileImageSize)
}
}
}
@ -83,6 +92,12 @@ open class TweetSearchLoader(
}
}
private fun getMastodonStatuses(account: AccountDetails, paging: Paging): Results {
val mastodon = account.newMicroBlogInstance(context, Mastodon::class.java)
if (query == null) throw MicroBlogException("Empty query")
return mastodon.search(query, true, paging)
}
private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): List<Status> {
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
if (query == null) throw MicroBlogException("Empty query")

View File

@ -596,6 +596,17 @@ object IntentUtils {
context.startActivity(intent)
}
fun openNetworkPublicTimeline(context: Context, accountKey: UserKey?) {
val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_NETWORK_PUBLIC_TIMELINE)
if (accountKey != null) {
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_KEY, accountKey.toString())
}
val intent = Intent(Intent.ACTION_VIEW, builder.build())
context.startActivity(intent)
}
fun openAccountsManager(context: Context) {
val intent = Intent()
val builder = Uri.Builder()

View File

@ -66,7 +66,6 @@ import org.mariotaku.sqliteqb.library.Selectable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.TwidereConstants.METADATA_KEY_EXTENSION_USE_JSON
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_USER_KEY
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.TwidereConstants.TAB_CODE_DIRECT_MESSAGES
import org.mariotaku.twidere.TwidereConstants.TAB_CODE_HOME_TIMELINE
@ -74,7 +73,6 @@ import org.mariotaku.twidere.TwidereConstants.TAB_CODE_NOTIFICATIONS_TIMELINE
import org.mariotaku.twidere.annotation.CustomTabType
import org.mariotaku.twidere.annotation.ProfileImageSize
import org.mariotaku.twidere.constant.CompatibilityConstants.EXTRA_ACCOUNT_ID
import org.mariotaku.twidere.constant.CompatibilityConstants.QUERY_PARAM_USER_ID
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEYS
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_PEBBLE_NOTIFICATION
@ -156,11 +154,6 @@ object Utils {
return contentType
}
fun getUserKeyParam(uri: Uri): String {
val paramUserKey = uri.getQueryParameter(QUERY_PARAM_USER_KEY) ?: return uri.getQueryParameter(QUERY_PARAM_USER_ID)
return paramUserKey
}
fun createStatusShareIntent(context: Context, status: ParcelableStatus): Intent {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"

View File

@ -24,6 +24,7 @@ object TwidereLinkMatcher {
addURI(AUTHORITY_MESSAGES, PATH_MESSAGES_CONVERSATION_INFO, LINK_ID_MESSAGES_CONVERSATION_INFO)
addURI(AUTHORITY_INTERACTIONS, null, LINK_ID_INTERACTIONS)
addURI(AUTHORITY_PUBLIC_TIMELINE, null, LINK_ID_PUBLIC_TIMELINE)
addURI(AUTHORITY_NETWORK_PUBLIC_TIMELINE, null, LINK_ID_NETWORK_PUBLIC_TIMELINE)
addURI(AUTHORITY_USER_LIST, null, LINK_ID_USER_LIST)
addURI(AUTHORITY_GROUP, null, LINK_ID_GROUP)
addURI(AUTHORITY_USER_LIST_TIMELINE, null, LINK_ID_USER_LIST_TIMELINE)

View File

@ -37,6 +37,10 @@
android:id="@id/public_timeline"
android:icon="@drawable/ic_action_quote"
android:title="@string/title_public_timeline"/>
<item
android:id="@+id/network_public_timeline"
android:icon="@drawable/ic_action_web"
android:title="@string/title_network_public_timeline"/>
</group>
<group
android:id="@+id/app_menu"