Open profiles from mentions

This commit is contained in:
Grishka 2022-02-14 03:26:07 +03:00
parent a91538582a
commit c1b3a4c463
6 changed files with 89 additions and 27 deletions

View File

@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountByID extends MastodonAPIRequest<Account>{
public GetAccountByID(String id){
super(HttpMethod.GET, "/accounts/"+id, Account.class);
}
}

View File

@ -34,6 +34,7 @@ import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
@ -71,6 +72,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2; import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.LoaderFragment; import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
@ -100,6 +102,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private EditText nameEdit, bioEdit; private EditText nameEdit, bioEdit;
private ProgressBar actionProgress; private ProgressBar actionProgress;
private FrameLayout[] tabViews; private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator;
private Account account; private Account account;
private String accountID; private String accountID;
@ -110,6 +113,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private boolean isInEditMode; private boolean isInEditMode;
private Uri editNewAvatar, editNewCover; private Uri editNewAvatar, editNewCover;
private String profileAccountID;
public ProfileFragment(){ public ProfileFragment(){
super(R.layout.loader_fragment_overlay_toolbar); super(R.layout.loader_fragment_overlay_toolbar);
@ -119,16 +123,25 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setRetainInstance(true); setRetainInstance(true);
accountID=getArguments().getString("account");
accountID=getArguments().getString("account");
if(getArguments().containsKey("profileAccount")){
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
loaded=true;
if(!isOwnProfile)
loadRelationship();
}else{
profileAccountID=getArguments().getString("profileAccountID");
if(!getArguments().getBoolean("noAutoLoad", false))
loadData();
}
} }
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
setHasOptionsMenu(true); setHasOptionsMenu(true);
if(!getArguments().getBoolean("noAutoLoad", false))
loadData();
} }
@Override @Override
@ -196,22 +209,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
pager.setAdapter(new ProfilePagerAdapter()); pager.setAdapter(new ProfilePagerAdapter());
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels; pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
if(getArguments().containsKey("profileAccount")){
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView();
dataLoaded();
if(!isOwnProfile)
loadRelationship();
}
scrollView.setScrollableChildSupplier(this::getScrollableRecyclerView); scrollView.setScrollableChildSupplier(this::getScrollableRecyclerView);
sizeWrapper.addView(content); sizeWrapper.addView(content);
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)); tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
tabbar.setTabTextSize(V.dp(16)); tabbar.setTabTextSize(V.dp(16));
new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){ tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override @Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
tab.setText(switch(position){ tab.setText(switch(position){
@ -222,7 +226,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
default -> throw new IllegalStateException(); default -> throw new IllegalStateException();
}); });
} }
}).attach(); });
cover.setForeground(coverGradient); cover.setForeground(coverGradient);
cover.setOutlineProvider(new ViewOutlineProvider(){ cover.setOutlineProvider(new ViewOutlineProvider(){
@ -236,11 +240,31 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatar.setOnClickListener(this::onAvatarClick); avatar.setOnClickListener(this::onAvatarClick);
cover.setOnClickListener(this::onCoverClick); cover.setOnClickListener(this::onCoverClick);
if(loaded){
bindHeaderView();
dataLoaded();
tabLayoutMediator.attach();
}
return sizeWrapper; return sizeWrapper;
} }
@Override @Override
protected void doLoadData(){ protected void doLoadData(){
currentRequest=new GetAccountByID(profileAccountID)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(Account result){
account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView();
dataLoaded();
tabLayoutMediator.attach();
if(!isOwnProfile)
loadRelationship();
}
})
.exec(accountID);
} }
@Override @Override
@ -250,11 +274,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void dataLoaded(){ public void dataLoaded(){
if(getActivity()==null)
return;
if(postsFragment==null){
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true); postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false); postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false); mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
aboutFragment=new ProfileAboutFragment(); aboutFragment=new ProfileAboutFragment();
aboutFragment.setFields(fields); aboutFragment.setFields(fields);
}
pager.getAdapter().notifyDataSetChanged(); pager.getAdapter().notifyDataSetChanged();
super.dataLoaded(); super.dataLoaded();
} }
@ -322,7 +350,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name.setText(ssb); name.setText(ssb);
setTitle(ssb); setTitle(ssb);
username.setText('@'+account.acct); username.setText('@'+account.acct);
bio.setText(HtmlParser.parse(account.note, account.emojis)); bio.setText(HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), accountID));
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount)); followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount)); followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount)); postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
@ -347,7 +375,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
fields.add(joined); fields.add(joined);
for(AccountField field:account.fields){ for(AccountField field:account.fields){
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis); field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), accountID);
field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class); field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class);
ssb=new SpannableStringBuilder(field.name); ssb=new SpannableStringBuilder(field.name);
HtmlParser.parseCustomEmoji(ssb, account.emojis); HtmlParser.parseCustomEmoji(ssb, account.emojis);

View File

@ -69,7 +69,7 @@ public abstract class StatusDisplayItem{
} }
items.add(new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID)); items.add(new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID));
if(!TextUtils.isEmpty(statusForContent.content)) if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis), fragment)); items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, accountID), fragment));
int photoIndex=0; int photoIndex=0;
int totalPhotos=0; int totalPhotos=0;
for(Attachment attachment:statusForContent.mediaAttachments){ for(Attachment attachment:statusForContent.mediaAttachments){

View File

@ -5,6 +5,7 @@ import android.text.Spanned;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@ -40,7 +41,7 @@ public class HtmlParser{
* @param emojis Custom emojis that are present in source as <code>:code:</code> * @param emojis Custom emojis that are present in source as <code>:code:</code>
* @return a spanned string * @return a spanned string
*/ */
public static SpannableStringBuilder parse(String source, List<Emoji> emojis){ public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, String accountID){
class SpanInfo{ class SpanInfo{
public Object span; public Object span;
public int start; public int start;
@ -53,6 +54,8 @@ public class HtmlParser{
} }
} }
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
final SpannableStringBuilder ssb=new SpannableStringBuilder(); final SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){ Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
private final ArrayList<SpanInfo> openSpans=new ArrayList<>(); private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
@ -65,15 +68,22 @@ public class HtmlParser{
Element el=(Element)node; Element el=(Element)node;
switch(el.nodeName()){ switch(el.nodeName()){
case "a" -> { case "a" -> {
String href=el.attr("href");
LinkSpan.Type linkType; LinkSpan.Type linkType;
if(el.hasClass("hashtag")){ if(el.hasClass("hashtag")){
linkType=LinkSpan.Type.HASHTAG; linkType=LinkSpan.Type.HASHTAG;
}else if(el.hasClass("mention")){ }else if(el.hasClass("mention")){
String id=idsByUrl.get(href);
if(id!=null){
linkType=LinkSpan.Type.MENTION; linkType=LinkSpan.Type.MENTION;
href=id;
}else{ }else{
linkType=LinkSpan.Type.URL; linkType=LinkSpan.Type.URL;
} }
openSpans.add(new SpanInfo(new LinkSpan(el.attr("href"), null, linkType), ssb.length(), el)); }else{
linkType=LinkSpan.Type.URL;
}
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
} }
case "br" -> ssb.append('\n'); case "br" -> ssb.append('\n');
case "span" -> { case "span" -> {

View File

@ -13,11 +13,13 @@ public class LinkSpan extends CharacterStyle {
private OnLinkClickListener listener; private OnLinkClickListener listener;
private String link; private String link;
private Type type; private Type type;
private String accountID;
public LinkSpan(String link, OnLinkClickListener listener, Type type) { public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
this.listener=listener; this.listener=listener;
this.link=link; this.link=link;
this.type=type; this.type=type;
this.accountID=accountID;
} }
public void setColor(int c){ public void setColor(int c){
@ -36,7 +38,8 @@ public class LinkSpan extends CharacterStyle {
public void onClick(Context context){ public void onClick(Context context){
switch(getType()){ switch(getType()){
case URL -> UiUtils.launchWebBrowser(context, link); case URL -> UiUtils.launchWebBrowser(context, link);
case HASHTAG, MENTION -> Toast.makeText(context, "Not implemented yet", Toast.LENGTH_SHORT).show(); case MENTION -> UiUtils.openProfileByID(context, accountID, link);
case HASHTAG -> Toast.makeText(context, "Not implemented yet", Toast.LENGTH_SHORT).show();
} }
} }

View File

@ -1,12 +1,14 @@
package org.joinmastodon.android.ui.utils; package org.joinmastodon.android.ui.utils;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
@ -17,6 +19,7 @@ import android.widget.TextView;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.ui.text.CustomEmojiSpan; import org.joinmastodon.android.ui.text.CustomEmojiSpan;
@ -29,6 +32,7 @@ import java.util.stream.Collectors;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
import androidx.annotation.ColorRes; import androidx.annotation.ColorRes;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import me.grishka.appkit.Nav;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@ -161,4 +165,11 @@ public class UiUtils{
ta.recycle(); ta.recycle();
return color; return color;
} }
public static void openProfileByID(Context context, String selfID, String id){
Bundle args=new Bundle();
args.putString("account", selfID);
args.putString("profileAccountID", id);
Nav.go((Activity)context, ProfileFragment.class, args);
}
} }