M3 redesign: search/discover
This commit is contained in:
@@ -71,7 +71,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||
handleURL(intent.getData());
|
||||
handleURL(intent.getData(), null);
|
||||
}else{
|
||||
maybeRequestNotificationsPermission();
|
||||
}
|
||||
@@ -114,29 +114,34 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||
handleURL(intent.getData());
|
||||
handleURL(intent.getData(), null);
|
||||
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
||||
}*/
|
||||
}
|
||||
|
||||
private void handleURL(Uri uri){
|
||||
public void handleURL(Uri uri, String accountID){
|
||||
if(uri==null)
|
||||
return;
|
||||
if(!"https".equals(uri.getScheme()) && !"http".equals(uri.getScheme()))
|
||||
return;
|
||||
if(!uri.getPath().startsWith("/@"))
|
||||
return;
|
||||
AccountSession session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
AccountSession session;
|
||||
if(accountID==null)
|
||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
else
|
||||
session=AccountSessionManager.get(accountID);
|
||||
if(session==null || !session.activated)
|
||||
return;
|
||||
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false);
|
||||
}
|
||||
|
||||
new GetSearchResults(uri.toString(), null, true)
|
||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||
new GetSearchResults(q, null, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", session.getID());
|
||||
args.putString("account", accountID);
|
||||
if(result.statuses!=null && !result.statuses.isEmpty()){
|
||||
args.putParcelable("status", Parcels.wrap(result.statuses.get(0)));
|
||||
Nav.go(MainActivity.this, ThreadFragment.class, args);
|
||||
@@ -144,7 +149,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
args.putParcelable("profileAccount", Parcels.wrap(result.accounts.get(0)));
|
||||
Nav.go(MainActivity.this, ProfileFragment.class, args);
|
||||
}else{
|
||||
Toast.makeText(MainActivity.this, R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(MainActivity.this, fromSearch ? R.string.no_search_results : R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +158,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
error.showToast(MainActivity.this);
|
||||
}
|
||||
})
|
||||
.wrapProgress(this, R.string.opening_link, true)
|
||||
.exec(session.getID());
|
||||
.wrapProgress(this, progressText, true)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void showFragmentForNotification(Notification notification, String accountID){
|
||||
|
||||
@@ -13,6 +13,11 @@ public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
||||
addQueryParameter("resolve", "true");
|
||||
}
|
||||
|
||||
public GetSearchResults limit(int limit){
|
||||
addQueryParameter("limit", String.valueOf(limit));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "/api/v2";
|
||||
|
||||
@@ -54,6 +54,7 @@ import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
@@ -309,6 +310,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
|
||||
protected int getMainAdapterOffset(){
|
||||
if(list.getAdapter() instanceof MergeRecyclerAdapter mergeAdapter){
|
||||
return mergeAdapter.getPositionForAdapter(adapter);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
sizeWrapper.addView(content);
|
||||
|
||||
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||
tabbar.setTabTextSize(V.dp(16));
|
||||
tabbar.setTabTextSize(V.dp(14));
|
||||
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
import org.parceler.Parcels;
|
||||
@@ -25,23 +17,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||
private LinearLayout searchLayout;
|
||||
private EditText searchEdit;
|
||||
private ImageButton clearSearchButton;
|
||||
private String currentQuery;
|
||||
private Runnable debouncer=()->{
|
||||
currentQuery=searchEdit.getText().toString();
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
}
|
||||
if(!TextUtils.isEmpty(currentQuery))
|
||||
loadData();
|
||||
};
|
||||
private boolean resultDelivered;
|
||||
private SearchViewHelper searchViewHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -53,32 +33,9 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
searchLayout=new LinearLayout(view.getContext());
|
||||
searchLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
searchEdit=new EditText(view.getContext());
|
||||
searchEdit.setHint(R.string.search_hint);
|
||||
searchEdit.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
|
||||
searchEdit.setBackground(null);
|
||||
searchEdit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
searchEdit.postDelayed(debouncer, 300);
|
||||
}));
|
||||
searchEdit.setImeActionLabel(null, EditorInfo.IME_ACTION_SEARCH);
|
||||
searchEdit.setOnEditorActionListener((v, actionId, event)->{
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
debouncer.run();
|
||||
return true;
|
||||
});
|
||||
searchLayout.addView(searchEdit, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
clearSearchButton=new ImageButton(view.getContext());
|
||||
clearSearchButton.setImageResource(R.drawable.ic_baseline_close_24);
|
||||
clearSearchButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(view.getContext(), R.attr.colorM3OnSurfaceVariant)));
|
||||
clearSearchButton.setBackground(UiUtils.getThemeDrawable(getToolbarContext(), android.R.attr.actionBarItemBackground));
|
||||
clearSearchButton.setOnClickListener(v->searchEdit.setText(""));
|
||||
searchLayout.addView(clearSearchButton, new LinearLayout.LayoutParams(V.dp(56), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_hint));
|
||||
searchViewHelper.setListeners(this::onQueryChanged, null);
|
||||
searchViewHelper.addDivider(contentView);
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
view.setBackgroundResource(R.drawable.bg_m3_surface3);
|
||||
@@ -104,11 +61,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
if(searchLayout.getParent()!=null)
|
||||
((ViewGroup) searchLayout.getParent()).removeView(searchLayout);
|
||||
getToolbar().addView(searchLayout, new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_m3_surface3);
|
||||
searchEdit.requestFocus();
|
||||
searchViewHelper.install(getToolbar());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,4 +85,14 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||
setResult(true, res);
|
||||
Nav.finish(this, false);
|
||||
}
|
||||
|
||||
private void onQueryChanged(String q){
|
||||
currentQuery=q;
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
}
|
||||
if(!TextUtils.isEmpty(currentQuery))
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +1,39 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.parceler.Parcels;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
|
||||
public DiscoverAccountsFragment(){
|
||||
super(20);
|
||||
}
|
||||
public class DiscoverAccountsFragment extends BaseAccountListFragment implements ScrollableToTop{
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.ACCOUNTS, accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(relationshipsRequest!=null){
|
||||
relationshipsRequest.cancel();
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
currentRequest=new GetFollowSuggestions(count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
List<AccountViewModel> accounts=result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList());
|
||||
onDataLoaded(accounts, false);
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -82,219 +41,14 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new AccountsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
outRect.bottom=outRect.left=outRect.right=V.dp(16);
|
||||
if(parent.getChildAdapterPosition(view)==0)
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
});
|
||||
((UsableRecyclerView)list).setDrawSelectorOnTop(true);
|
||||
}
|
||||
|
||||
private void loadRelationships(){
|
||||
relationships=Collections.emptyMap();
|
||||
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||
relationshipsRequest.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequest=null;
|
||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof AccountViewHolder avh)
|
||||
avh.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
if(relationshipsRequest!=null){
|
||||
relationshipsRequest.cancel();
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, adapter);
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new AccountViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return 2+data.get(position).emojiHelper.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
AccountWrapper item=data.get(position);
|
||||
if(image==0)
|
||||
return item.avaRequest;
|
||||
else if(image==1)
|
||||
return item.coverRequest;
|
||||
else
|
||||
return item.emojiHelper.getImageRequest(image-2);
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final ImageView cover, avatar;
|
||||
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||
private final ProgressBarButton actionButton;
|
||||
private final ProgressBar actionProgress;
|
||||
private final View actionWrap;
|
||||
|
||||
private Relationship relationship;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(getActivity(), R.layout.item_discover_account, list);
|
||||
cover=findViewById(R.id.cover);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
followersCount=findViewById(R.id.followers_count);
|
||||
followersLabel=findViewById(R.id.followers_label);
|
||||
followingCount=findViewById(R.id.following_count);
|
||||
followingLabel=findViewById(R.id.following_label);
|
||||
postsCount=findViewById(R.id.posts_count);
|
||||
postsLabel=findViewById(R.id.posts_label);
|
||||
actionButton=findViewById(R.id.action_btn);
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
||||
itemView.setClipToOutline(true);
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||
cover.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(AccountWrapper item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.account.acct);
|
||||
bio.setText(item.parsedBio);
|
||||
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else if(index==1){
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
}
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
private void onActionButtonClick(View v){
|
||||
itemView.setHasTransientState(true);
|
||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||
itemView.setHasTransientState(false);
|
||||
relationships.put(item.account.id, rel);
|
||||
rebind();
|
||||
});
|
||||
}
|
||||
|
||||
private void setActionProgressVisible(boolean visible){
|
||||
actionButton.setTextVisible(!visible);
|
||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
actionButton.setClickable(!visible);
|
||||
}
|
||||
}
|
||||
|
||||
protected class AccountWrapper{
|
||||
public Account account;
|
||||
public ImageLoaderRequest avaRequest, coverRequest;
|
||||
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
public CharSequence parsedName, parsedBio;
|
||||
|
||||
public AccountWrapper(Account account){
|
||||
this.account=account;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(account.emojis.isEmpty()){
|
||||
parsedName=account.displayName;
|
||||
}else{
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,19 @@ package org.joinmastodon.android.fragments.discover;
|
||||
import android.app.Fragment;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
@@ -28,22 +25,24 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
|
||||
private static final int QUERY_RESULT=937;
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
private FrameLayout[] tabViews;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
private EditText searchEdit;
|
||||
private boolean searchActive;
|
||||
private FrameLayout searchView;
|
||||
private ImageButton searchBack, searchClear;
|
||||
private ProgressBar searchProgress;
|
||||
private ImageButton searchBack;
|
||||
private TextView searchText;
|
||||
private View tabsDivider;
|
||||
|
||||
private DiscoverPostsFragment postsFragment;
|
||||
private TrendingHashtagsFragment hashtagsFragment;
|
||||
@@ -53,7 +52,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private LocalTimelineFragment localTimelineFragment;
|
||||
|
||||
private String accountID;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
private String currentQuery;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -88,8 +87,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabViews[i]=tabView;
|
||||
}
|
||||
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||
tabLayout.setTabTextSize(V.dp(14));
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
@@ -146,7 +145,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
case 4 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
}
|
||||
});
|
||||
tabLayoutMediator.attach();
|
||||
@@ -163,44 +161,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
});
|
||||
|
||||
searchEdit=view.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
if(s.length()==0){
|
||||
V.setVisibilityAnimated(searchClear, View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
searchEdit.postDelayed(searchDebouncer, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if(s.length()==0){
|
||||
V.setVisibilityAnimated(searchClear, View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchView=view.findViewById(R.id.search_fragment);
|
||||
if(searchFragment==null){
|
||||
searchFragment=new SearchFragment();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
searchFragment.setArguments(args);
|
||||
searchFragment.setProgressVisibilityListener(this::onSearchProgressVisibilityChanged);
|
||||
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
|
||||
}
|
||||
|
||||
searchBack=view.findViewById(R.id.search_back);
|
||||
searchClear=view.findViewById(R.id.search_clear);
|
||||
searchProgress=view.findViewById(R.id.search_progress);
|
||||
searchText=view.findViewById(R.id.search_text);
|
||||
searchBack.setEnabled(searchActive);
|
||||
searchBack.setImportantForAccessibility(searchActive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
searchBack.setOnClickListener(v->exitSearch());
|
||||
@@ -210,11 +181,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
searchView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
searchClear.setOnClickListener(v->{
|
||||
searchEdit.setText("");
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
onSearchChangedDebounced();
|
||||
|
||||
View searchWrap=view.findViewById(R.id.search_wrap);
|
||||
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
|
||||
searchWrap.setClipToOutline(true);
|
||||
searchText.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
if(!TextUtils.isEmpty(currentQuery)){
|
||||
args.putString("query", currentQuery);
|
||||
}
|
||||
Nav.goForResult(getActivity(), SearchQueryFragment.class, args, QUERY_RESULT, DiscoverFragment.this);
|
||||
});
|
||||
tabsDivider=view.findViewById(R.id.tabs_divider);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -233,35 +212,32 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
postsFragment.loadData();
|
||||
}
|
||||
|
||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||
if(!searchActive && hasFocus){
|
||||
private void enterSearch(){
|
||||
if(!searchActive){
|
||||
searchActive=true;
|
||||
pager.setVisibility(View.GONE);
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
searchView.setVisibility(View.VISIBLE);
|
||||
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
||||
searchBack.setImageResource(R.drawable.ic_arrow_back);
|
||||
searchBack.setEnabled(true);
|
||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
tabsDivider.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void exitSearch(){
|
||||
if(!searchActive)
|
||||
return;
|
||||
searchActive=false;
|
||||
pager.setVisibility(View.VISIBLE);
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
searchView.setVisibility(View.GONE);
|
||||
searchEdit.clearFocus();
|
||||
searchEdit.setText("");
|
||||
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
|
||||
searchText.setText(R.string.search_mastodon);
|
||||
searchBack.setImageResource(R.drawable.ic_search_24px);
|
||||
searchBack.setEnabled(false);
|
||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
tabsDivider.setVisibility(View.VISIBLE);
|
||||
currentQuery=null;
|
||||
}
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
@@ -284,23 +260,20 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onSearchChangedDebounced(){
|
||||
searchFragment.setQuery(searchEdit.getText().toString());
|
||||
}
|
||||
|
||||
private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
onSearchChangedDebounced();
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSearchProgressVisibilityChanged(boolean visible){
|
||||
V.setVisibilityAnimated(searchProgress, visible ? View.VISIBLE : View.INVISIBLE);
|
||||
if(searchEdit.length()>0)
|
||||
V.setVisibilityAnimated(searchClear, visible ? View.INVISIBLE : View.VISIBLE);
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
if(reqCode==QUERY_RESULT && success){
|
||||
enterSearch();
|
||||
currentQuery=result.getString("query");
|
||||
SearchResult.Type type;
|
||||
if(result.containsKey("filter")){
|
||||
type=SearchResult.Type.values()[result.getInt("filter")];
|
||||
}else{
|
||||
type=null;
|
||||
}
|
||||
searchFragment.setQuery(currentQuery, type);
|
||||
searchText.setText(currentQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -12,32 +13,43 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
private UsableRecyclerView cardsList;
|
||||
private ArrayList<CardViewModel> top3=new ArrayList<>();
|
||||
private CardLinksAdapter cardsAdapter;
|
||||
|
||||
public DiscoverNewsFragment(){
|
||||
super(10);
|
||||
@@ -47,6 +59,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,10 +68,14 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Card> result){
|
||||
imageRequests=result.stream()
|
||||
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
||||
.collect(Collectors.toList());
|
||||
onDataLoaded(result, false);
|
||||
top3.clear();
|
||||
top3.addAll(result.subList(0, Math.min(3, result.size())).stream().map(card->new CardViewModel(card, 280, 140)).collect(Collectors.toList()));
|
||||
cardsAdapter.notifyDataSetChanged();
|
||||
|
||||
onDataLoaded(result.subList(top3.size(), result.size()).stream()
|
||||
.map(card->new CardViewModel(card, 56, 56))
|
||||
.collect(Collectors.toList()), false);
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -66,14 +83,27 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new LinksAdapter();
|
||||
}
|
||||
cardsList=new UsableRecyclerView(getActivity());
|
||||
cardsList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
||||
ListImageLoaderWrapper cardsImageLoader=new ListImageLoaderWrapper(getActivity(), cardsList, new RecyclerViewDelegate(cardsList), this);
|
||||
cardsList.setAdapter(cardsAdapter=new CardLinksAdapter(cardsImageLoader, top3));
|
||||
cardsList.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(256)));
|
||||
cardsList.setPadding(V.dp(16), V.dp(8), 0, 0);
|
||||
cardsList.setClipToPadding(false);
|
||||
cardsList.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
outRect.right=V.dp(16);
|
||||
}
|
||||
});
|
||||
cardsList.setSelector(R.drawable.bg_rect_12dp_ripple);
|
||||
cardsList.setDrawSelectorOnTop(true);
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 0, 0));
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(cardsList));
|
||||
mergeAdapter.addAdapter(new LinksAdapter(imgLoader, data));
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,14 +111,17 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public LinksAdapter(){
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<BaseLinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
private final List<CardViewModel> data;
|
||||
|
||||
public LinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||
super(imgLoader);
|
||||
this.data=data;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public LinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new LinkViewHolder();
|
||||
}
|
||||
|
||||
@@ -98,46 +131,51 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(LinkViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
public void onBindViewHolder(BaseLinkViewHolder holder, int position){
|
||||
holder.bind(data.get(position).card);
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return imageRequests.get(position)==null ? 0 : 1;
|
||||
return data.get(position).imageRequest==null ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return imageRequests.get(position);
|
||||
return data.get(position).imageRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
||||
private final TextView name, title, subtitle;
|
||||
private final ImageView photo;
|
||||
private class CardLinksAdapter extends LinksAdapter{
|
||||
public CardLinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||
super(imgLoader, data);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new LinkCardViewHolder();
|
||||
}
|
||||
}
|
||||
|
||||
private class BaseLinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
||||
protected final TextView name, title;
|
||||
protected final ImageView photo;
|
||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||
private boolean didClear;
|
||||
|
||||
public LinkViewHolder(){
|
||||
super(getActivity(), R.layout.item_trending_link, list);
|
||||
public BaseLinkViewHolder(int layout){
|
||||
super(getActivity(), layout, list);
|
||||
name=findViewById(R.id.name);
|
||||
title=findViewById(R.id.title);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
photo=findViewById(R.id.photo);
|
||||
photo.setOutlineProvider(OutlineProviders.roundedRect(2));
|
||||
photo.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Card item){
|
||||
name.setText(item.providerName);
|
||||
title.setText(item.title);
|
||||
int num=item.history.get(0).uses;
|
||||
if(item.history.size()>1)
|
||||
num+=item.history.get(1).uses;
|
||||
subtitle.setText(getResources().getQuantityString(R.plurals.discussed_x_times, num, num));
|
||||
crossfadeDrawable.setSize(item.width, item.height);
|
||||
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
@@ -164,4 +202,20 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkViewHolder extends BaseLinkViewHolder{
|
||||
public LinkViewHolder(){
|
||||
super(R.layout.item_trending_link);
|
||||
photo.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
photo.setClipToOutline(true);
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkCardViewHolder extends BaseLinkViewHolder{
|
||||
public LinkCardViewHolder(){
|
||||
super(R.layout.item_trending_link_card);
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
itemView.setClipToOutline(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
@@ -10,10 +9,18 @@ import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class DiscoverPostsFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS, accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
@@ -22,13 +29,16 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, adapter);
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,21 @@ import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class LocalTimelineFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
|
||||
private String maxID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||
@@ -29,14 +38,17 @@ public class LocalTimelineFragment extends StatusListFragment{
|
||||
boolean empty=result.isEmpty();
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
||||
onDataLoaded(result, !empty);
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, adapter);
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.discover;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
@@ -22,7 +21,6 @@ import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -33,11 +31,9 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
@@ -45,12 +41,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
private List<StatusDisplayItem> prevDisplayItems;
|
||||
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||
private List<SearchResult> unfilteredResults=Collections.emptyList();
|
||||
private HideableSingleViewRecyclerAdapter headerAdapter;
|
||||
private ProgressVisibilityListener progressVisibilityListener;
|
||||
private InputMethodManager imm;
|
||||
|
||||
private TabLayout tabLayout;
|
||||
|
||||
public SearchFragment(){
|
||||
setLayout(R.layout.fragment_search);
|
||||
}
|
||||
@@ -60,6 +52,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
super.onCreate(savedInstanceState);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
setEmptyText(R.string.no_search_results);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@@ -118,51 +111,48 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(isInRecentMode()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
unfilteredResults=sr;
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
onDataLoaded(sr, false);
|
||||
});
|
||||
GetSearchResults.Type type;
|
||||
if(currentFilter.size()==1){
|
||||
type=switch(currentFilter.iterator().next()){
|
||||
case ACCOUNT -> GetSearchResults.Type.ACCOUNTS;
|
||||
case HASHTAG -> GetSearchResults.Type.HASHTAGS;
|
||||
case STATUS -> GetSearchResults.Type.STATUSES;
|
||||
};
|
||||
}else{
|
||||
progressVisibilityListener.onProgressVisibilityChanged(true);
|
||||
currentRequest=new GetSearchResults(currentQuery, null, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
ArrayList<SearchResult> results=new ArrayList<>();
|
||||
if(result.accounts!=null){
|
||||
for(Account acc:result.accounts)
|
||||
results.add(new SearchResult(acc));
|
||||
}
|
||||
if(result.hashtags!=null){
|
||||
for(Hashtag tag:result.hashtags)
|
||||
results.add(new SearchResult(tag));
|
||||
}
|
||||
if(result.statuses!=null){
|
||||
for(Status status:result.statuses)
|
||||
results.add(new SearchResult(status));
|
||||
}
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
unfilteredResults=results;
|
||||
onDataLoaded(filterSearchResults(results), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
currentRequest=null;
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
return;
|
||||
error.showToast(a);
|
||||
if(progressVisibilityListener!=null)
|
||||
progressVisibilityListener.onProgressVisibilityChanged(false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
type=null;
|
||||
}
|
||||
currentRequest=new GetSearchResults(currentQuery, type, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
ArrayList<SearchResult> results=new ArrayList<>();
|
||||
if(result.accounts!=null){
|
||||
for(Account acc:result.accounts)
|
||||
results.add(new SearchResult(acc));
|
||||
}
|
||||
if(result.hashtags!=null){
|
||||
for(Hashtag tag:result.hashtags)
|
||||
results.add(new SearchResult(tag));
|
||||
}
|
||||
if(result.statuses!=null){
|
||||
for(Status status:result.statuses)
|
||||
results.add(new SearchResult(status));
|
||||
}
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
unfilteredResults=results;
|
||||
onDataLoaded(filterSearchResults(results), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
currentRequest=null;
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
return;
|
||||
error.showToast(a);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,86 +162,30 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
return;
|
||||
}
|
||||
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
|
||||
boolean recent=isInRecentMode();
|
||||
if(recent!=headerAdapter.isVisible())
|
||||
headerAdapter.setVisible(recent);
|
||||
imgLoader.forceUpdateImages();
|
||||
prevDisplayItems=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDataLoaded(List<SearchResult> d, boolean more){
|
||||
super.onDataLoaded(d, more);
|
||||
if(progressVisibilityListener!=null)
|
||||
progressVisibilityListener.onProgressVisibilityChanged(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.search_all));
|
||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.search_people));
|
||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.hashtags));
|
||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.posts));
|
||||
for(int i=0;i<tabLayout.getTabCount();i++){
|
||||
tabLayout.getTabAt(i).view.textView.setAllCaps(true);
|
||||
}
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab){
|
||||
setFilter(switch(tab.getPosition()){
|
||||
case 0 -> EnumSet.allOf(SearchResult.Type.class);
|
||||
case 1 -> EnumSet.of(SearchResult.Type.ACCOUNT);
|
||||
case 2 -> EnumSet.of(SearchResult.Type.HASHTAG);
|
||||
case 3 -> EnumSet.of(SearchResult.Type.STATUS);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+tab.getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab){
|
||||
scrollToTop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
View header=getActivity().getLayoutInflater().inflate(R.layout.item_recent_searches_header, list, false);
|
||||
header.findViewById(R.id.clear).setOnClickListener(this::onClearRecentClick);
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(headerAdapter=new HideableSingleViewRecyclerAdapter(header));
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
headerAdapter.setVisible(isInRecentMode());
|
||||
return adapter;
|
||||
}
|
||||
|
||||
public void setQuery(String q){
|
||||
if(Objects.equals(q, currentQuery) || q.isBlank())
|
||||
public void setQuery(String q, SearchResult.Type filter){
|
||||
if(q.isBlank())
|
||||
return;
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
}
|
||||
currentQuery=q;
|
||||
if(filter==null)
|
||||
currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||
else
|
||||
currentFilter=EnumSet.of(filter);
|
||||
refreshing=true;
|
||||
doLoadData(0, 0);
|
||||
loadData();
|
||||
}
|
||||
|
||||
private void setFilter(EnumSet<SearchResult.Type> filter){
|
||||
if(filter.equals(currentFilter))
|
||||
return;
|
||||
currentFilter=filter;
|
||||
if(isInRecentMode())
|
||||
return;
|
||||
// This can be optimized by not rebuilding display items every time filter is changed, but I'm too lazy
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
refreshing=true;
|
||||
@@ -273,23 +207,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
return null;
|
||||
}
|
||||
|
||||
private void onClearRecentClick(View v){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().clearRecentSearches();
|
||||
if(isInRecentMode()){
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
refreshing=true;
|
||||
onDataLoaded(unfilteredResults=Collections.emptyList(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInRecentMode(){
|
||||
return TextUtils.isEmpty(currentQuery);
|
||||
}
|
||||
|
||||
public void setProgressVisibilityListener(ProgressVisibilityListener progressVisibilityListener){
|
||||
this.progressVisibilityListener=progressVisibilityListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStarted(){
|
||||
super.onScrollStarted();
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Fragment;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.RoundedCorner;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.CustomTransitionsFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment, OnBackPressedListener{
|
||||
private static final Pattern HASHTAG_REGEX=Pattern.compile("^(\\w*[a-zA-Z·]\\w*)$", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern USERNAME_REGEX=Pattern.compile("^@?([a-z0-9_-]+)(@[^\\s]+)?$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||
private HideableSingleViewRecyclerAdapter recentsHeader;
|
||||
private ListItem<Void> openUrlItem, goToHashtagItem, goToAccountItem, goToStatusSearchItem, goToAccountSearchItem;
|
||||
private ArrayList<ListItem<Void>> topOptions=new ArrayList<>();
|
||||
private GenericListItemsAdapter<Void> topOptionsAdapter;
|
||||
|
||||
private String accountID;
|
||||
private SearchViewHelper searchViewHelper;
|
||||
private String currentQuery;
|
||||
private LayerDrawable navigationIcon;
|
||||
private Drawable searchIcon, backIcon;
|
||||
|
||||
public SearchQueryFragment(){
|
||||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
setRefreshEnabled(false);
|
||||
setEmptyText("");
|
||||
|
||||
openUrlItem=new ListItem<>(R.string.search_open_url, 0, R.drawable.ic_link_24px, this::onOpenURLClick);
|
||||
goToHashtagItem=new ListItem<>("", null, R.drawable.ic_tag_24px, this::onGoToHashtagClick);
|
||||
goToAccountItem=new ListItem<>("", null, R.drawable.ic_person_24px, this::onGoToAccountClick);
|
||||
goToStatusSearchItem=new ListItem<>("", null, R.drawable.ic_search_24px, this::onGoToStatusSearchClick);
|
||||
goToAccountSearchItem=new ListItem<>("", null, R.drawable.ic_group_24px, this::onGoToAccountSearchClick);
|
||||
currentQuery=getArguments().getString("query");
|
||||
|
||||
dataLoaded();
|
||||
doLoadData(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(isInRecentMode()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(results->{
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
|
||||
onDataLoaded(results.stream().map(sr->{
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
||||
if(sr.type==SearchResult.Type.HASHTAG){
|
||||
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||
}
|
||||
return vm;
|
||||
}).collect(Collectors.toList()), false);
|
||||
recentsHeader.setVisible(!data.isEmpty());
|
||||
});
|
||||
}else{
|
||||
currentRequest=new GetSearchResults(currentQuery, null, false)
|
||||
.limit(2)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
onDataLoaded(Stream.of(result.hashtags.stream().map(SearchResult::new), result.accounts.stream().map(SearchResult::new))
|
||||
.flatMap(Function.identity())
|
||||
.map(sr->{
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
||||
if(sr.type==SearchResult.Type.HASHTAG){
|
||||
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||
}
|
||||
return vm;
|
||||
})
|
||||
.collect(Collectors.toList()), false);
|
||||
recentsHeader.setVisible(false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<?> getAdapter(){
|
||||
View header=getActivity().getLayoutInflater().inflate(R.layout.display_item_section_header, list, false);
|
||||
TextView title=header.findViewById(R.id.title);
|
||||
Button action=header.findViewById(R.id.action_btn);
|
||||
title.setText(R.string.recent_searches);
|
||||
action.setText(R.string.clear_all);
|
||||
action.setOnClickListener(v->onClearRecentClick());
|
||||
recentsHeader=new HideableSingleViewRecyclerAdapter(header);
|
||||
|
||||
mergeAdapter.addAdapter(recentsHeader);
|
||||
mergeAdapter.addAdapter(topOptionsAdapter=new GenericListItemsAdapter<>(topOptions));
|
||||
mergeAdapter.addAdapter(new SearchResultsAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_mastodon));
|
||||
searchViewHelper.setListeners(this::onQueryChanged, this::onQueryChangedNoDebounce);
|
||||
searchViewHelper.addDivider(contentView);
|
||||
searchViewHelper.setEnterCallback(this::onSearchViewEnter);
|
||||
|
||||
navigationIcon=new LayerDrawable(new Drawable[]{
|
||||
searchIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_search_24px, getToolbarContext().getTheme()).mutate(),
|
||||
backIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_arrow_back, getToolbarContext().getTheme()).mutate()
|
||||
}){
|
||||
@Override
|
||||
public Drawable mutate(){
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
view.setBackgroundResource(R.drawable.bg_m3_surface3);
|
||||
int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Surface, R.attr.colorM3Primary, 0.11f);
|
||||
setStatusBarColor(color);
|
||||
setNavigationBarColor(color);
|
||||
if(currentQuery!=null){
|
||||
searchViewHelper.setQuery(currentQuery);
|
||||
searchIcon.setAlpha(0);
|
||||
}
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 1, 0, 0, vh->!isInRecentMode() && vh.getAbsoluteAdapterPosition()==topOptions.size()-1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
((ViewGroup.MarginLayoutParams)getToolbar().getLayoutParams()).topMargin=V.dp(8);
|
||||
searchViewHelper.install(getToolbar());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean wantsElevationOnScrollEffect(){
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onQueryChanged(String q){
|
||||
currentQuery=q;
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
}
|
||||
refreshing=true;
|
||||
doLoadData(0, 0);
|
||||
}
|
||||
|
||||
private void onQueryChangedNoDebounce(String q){
|
||||
updateTopOptions(q);
|
||||
if(!TextUtils.isEmpty(q)){
|
||||
recentsHeader.setVisible(false);
|
||||
}
|
||||
data.clear();
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void updateTopOptions(String q){
|
||||
topOptions.clear();
|
||||
// https://github.com/mastodon/mastodon/blob/a985d587e13494b78ef2879e4d97f78a2df693db/app/javascript/mastodon/features/compose/components/search.jsx#L233
|
||||
String trimmedValue=q.trim();
|
||||
if(trimmedValue.length()>0){
|
||||
boolean couldBeURL=trimmedValue.startsWith("https://") && !trimmedValue.contains(" ");
|
||||
if(couldBeURL){
|
||||
topOptions.add(openUrlItem);
|
||||
}
|
||||
|
||||
boolean couldBeHashtag=(trimmedValue.startsWith("#") && trimmedValue.length()>1 && !trimmedValue.contains(" ")) || HASHTAG_REGEX.matcher(trimmedValue).find();
|
||||
if(couldBeHashtag){
|
||||
String tag=trimmedValue.startsWith("#") ? trimmedValue.substring(1) : trimmedValue;
|
||||
goToHashtagItem.title=getString(R.string.posts_matching_hashtag, "#"+tag);
|
||||
topOptions.add(goToHashtagItem);
|
||||
}
|
||||
|
||||
Matcher usernameMatcher=USERNAME_REGEX.matcher(trimmedValue);
|
||||
if(usernameMatcher.find()){
|
||||
String username="@"+usernameMatcher.group(1);
|
||||
String atDomain=usernameMatcher.group(2);
|
||||
if(atDomain==null){
|
||||
username+="@"+AccountSessionManager.get(accountID).domain;
|
||||
}
|
||||
goToAccountItem.title=getString(R.string.search_go_to_account, username);
|
||||
topOptions.add(goToAccountItem);
|
||||
}
|
||||
|
||||
goToStatusSearchItem.title=getString(R.string.posts_matching_string, trimmedValue);
|
||||
topOptions.add(goToStatusSearchItem);
|
||||
goToAccountSearchItem.title=getString(R.string.accounts_matching_string, trimmedValue);
|
||||
topOptions.add(goToAccountSearchItem);
|
||||
}
|
||||
topOptionsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator onCreateEnterTransition(View prev, View container){
|
||||
return createTransition(prev, container, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator onCreateExitTransition(View prev, View container){
|
||||
return createTransition(prev, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsCustomNavigationIcon(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable getNavigationIconDrawable(){
|
||||
return navigationIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
getActivity().getSystemService(InputMethodManager.class).showSoftInput(getActivity().getCurrentFocus(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(getActivity().getWindow().getDecorView().getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private Animator createTransition(View prev, View container, boolean enter){
|
||||
int[] loc={0, 0};
|
||||
View searchBtn=prev.findViewById(R.id.search_wrap);
|
||||
searchBtn.getLocationInWindow(loc);
|
||||
int btnLeft=loc[0], btnTop=loc[1];
|
||||
container.getLocationInWindow(loc);
|
||||
int offX=btnLeft-loc[0], offY=btnTop-loc[1];
|
||||
|
||||
float screenRadius;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
|
||||
WindowInsets insets=container.getRootWindowInsets();
|
||||
screenRadius=Math.min(
|
||||
Math.min(insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT).getRadius(), insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT).getRadius()),
|
||||
Math.min(insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT).getRadius(), insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT).getRadius())
|
||||
);
|
||||
}else{
|
||||
screenRadius=0;
|
||||
}
|
||||
float buttonRadius=V.dp(26);
|
||||
|
||||
Rect buttonBounds=new Rect(offX, offY, offX+searchBtn.getWidth(), offY+searchBtn.getHeight());
|
||||
Rect containerBounds=new Rect(0, 0, container.getWidth(), container.getHeight());
|
||||
AnimatableOutlineProvider outlineProvider=new AnimatableOutlineProvider(enter ? buttonBounds : containerBounds, enter ? containerBounds : buttonBounds, enter ? buttonRadius : screenRadius);
|
||||
container.setOutlineProvider(outlineProvider);
|
||||
container.setClipToOutline(true);
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
ObjectAnimator boundsAnim;
|
||||
|
||||
Toolbar toolbar=getToolbar();
|
||||
float toolbarTX=offX-toolbar.getX();
|
||||
float toolbarTY=offY-toolbar.getY()+(searchBtn.getHeight()-toolbar.getHeight())/2f;
|
||||
ArrayList<Animator> anims=new ArrayList<>();
|
||||
anims.add(boundsAnim=ObjectAnimator.ofFloat(outlineProvider, "boundsFraction", 0f, 1f));
|
||||
anims.add(ObjectAnimator.ofFloat(outlineProvider, "radius", enter ? buttonRadius : screenRadius, enter ? screenRadius : buttonRadius));
|
||||
anims.add(ObjectAnimator.ofFloat(toolbar, View.TRANSLATION_X, enter ? toolbarTX : 0, enter ? 0 : toolbarTX));
|
||||
anims.add(ObjectAnimator.ofFloat(toolbar, View.TRANSLATION_Y, enter ? toolbarTY : 0, enter ? 0 : toolbarTY));
|
||||
anims.add(ObjectAnimator.ofFloat(searchViewHelper.getSearchLayout(), View.TRANSLATION_X, enter ? V.dp(-4) : 0, enter ? 0 : V.dp(-4)));
|
||||
anims.add(ObjectAnimator.ofFloat(searchViewHelper.getDivider(), View.ALPHA, enter ? 0 : 1, enter ? 1 : 0));
|
||||
View parentContent=prev.findViewById(R.id.discover_content);
|
||||
View parentContentParent=(View) parentContent.getParent();
|
||||
parentContentParent.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Surface));
|
||||
if(enter){
|
||||
anims.add(ObjectAnimator.ofFloat(contentWrap, View.TRANSLATION_Y, V.dp(-16), 0));
|
||||
}else{
|
||||
}
|
||||
anims.add(ObjectAnimator.ofFloat(contentWrap, View.ALPHA, enter ? 0 : 1, enter ? 1 : 0));
|
||||
for(Animator anim:anims){
|
||||
anim.setDuration(enter ? 700 : 300);
|
||||
}
|
||||
if(TextUtils.isEmpty(currentQuery)){
|
||||
anims.add(ObjectAnimator.ofInt(searchIcon, "alpha", enter ? 255 : 0, enter ? 0 : 255).setDuration(200));
|
||||
anims.add(ObjectAnimator.ofInt(backIcon, "alpha", enter ? 0 : 255, enter ? 255 : 0).setDuration(200));
|
||||
}
|
||||
ObjectAnimator parentContentFade;
|
||||
anims.add(parentContentFade=ObjectAnimator.ofFloat(parentContent, View.ALPHA, enter ? 1 : 0, enter ? 0 : 1).setDuration(enter ? 350 : 250));
|
||||
if(!enter){
|
||||
parentContentFade.setStartDelay(50);
|
||||
ObjectAnimator parentContentTY;
|
||||
anims.add(parentContentTY=ObjectAnimator.ofFloat(parentContent, View.TRANSLATION_Y, V.dp(16), 0).setDuration(250));
|
||||
parentContentTY.setStartDelay(50);
|
||||
}
|
||||
|
||||
set.playTogether(anims);
|
||||
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_emphasized_decelerate));
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
container.setOutlineProvider(null);
|
||||
container.setClipToOutline(false);
|
||||
parentContentParent.setBackground(null);
|
||||
}
|
||||
});
|
||||
boundsAnim.addUpdateListener(animation->{
|
||||
container.invalidateOutline();
|
||||
navigationIcon.invalidateSelf();
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
private void openHashtag(SearchResult res){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||
}
|
||||
|
||||
private boolean isInRecentMode(){
|
||||
return TextUtils.isEmpty(currentQuery);
|
||||
}
|
||||
|
||||
private void onSearchViewEnter(){
|
||||
deliverResult(currentQuery, null);
|
||||
}
|
||||
|
||||
private void onOpenURLClick(){
|
||||
((MainActivity)getActivity()).handleURL(Uri.parse(searchViewHelper.getQuery()), accountID);
|
||||
}
|
||||
|
||||
private void onGoToHashtagClick(){
|
||||
String q=searchViewHelper.getQuery();
|
||||
if(q.startsWith("#"))
|
||||
q=q.substring(1);
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
|
||||
}
|
||||
|
||||
private void onGoToAccountClick(){
|
||||
String q=searchViewHelper.getQuery();
|
||||
if(!q.startsWith("@")){
|
||||
q="@"+q;
|
||||
}
|
||||
if(q.lastIndexOf('@')==0){
|
||||
q+="@"+AccountSessionManager.get(accountID).domain;
|
||||
}
|
||||
((MainActivity)getActivity()).openSearchQuery(q, accountID, R.string.loading, true);
|
||||
}
|
||||
|
||||
private void onGoToStatusSearchClick(){
|
||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
||||
}
|
||||
|
||||
private void onGoToAccountSearchClick(){
|
||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
||||
}
|
||||
|
||||
private void onClearRecentClick(){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().clearRecentSearches();
|
||||
if(isInRecentMode()){
|
||||
data.clear();
|
||||
recentsHeader.setVisible(false);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverResult(String query, SearchResult.Type typeFilter){
|
||||
Bundle res=new Bundle();
|
||||
res.putString("query", query);
|
||||
if(typeFilter!=null)
|
||||
res.putInt("filter", typeFilter.ordinal());
|
||||
setResult(true, res);
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
String initialQuery=getArguments().getString("query");
|
||||
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
|
||||
currentQuery=initialQuery;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class AnimatableOutlineProvider extends ViewOutlineProvider{
|
||||
private float boundsFraction, radius;
|
||||
private final Rect boundsFrom, boundsTo;
|
||||
|
||||
private AnimatableOutlineProvider(Rect boundsFrom, Rect boundsTo, float radius){
|
||||
this.boundsFrom=boundsFrom;
|
||||
this.boundsTo=boundsTo;
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(
|
||||
UiUtils.lerp(boundsFrom.left, boundsTo.left, boundsFraction),
|
||||
UiUtils.lerp(boundsFrom.top, boundsTo.top, boundsFraction),
|
||||
UiUtils.lerp(boundsFrom.right, boundsTo.right, boundsFraction),
|
||||
UiUtils.lerp(boundsFrom.bottom, boundsTo.bottom, boundsFraction),
|
||||
radius
|
||||
);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getBoundsFraction(){
|
||||
return boundsFraction;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setBoundsFraction(float boundsFraction){
|
||||
this.boundsFraction=boundsFraction;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getRadius(){
|
||||
return radius;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setRadius(float radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchResultsAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public SearchResultsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
if(viewType==R.id.list_item_account){
|
||||
return new CustomAccountViewHolder(SearchQueryFragment.this, parent, null);
|
||||
}else if(viewType==R.id.list_item_simple){
|
||||
return new SimpleListItemViewHolder(parent.getContext(), parent);
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
|
||||
if(holder instanceof CustomAccountViewHolder avh){
|
||||
avh.bind(data.get(position).account);
|
||||
avh.searchResult=data.get(position).result;
|
||||
}else if(holder instanceof SimpleListItemViewHolder ivh){
|
||||
ivh.bind(data.get(position).hashtagItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return switch(data.get(position).result.type){
|
||||
case ACCOUNT -> R.id.list_item_account;
|
||||
case HASHTAG -> R.id.list_item_simple;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+data.get(position).result.type);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
SearchResultViewModel vm=data.get(position);
|
||||
if(vm.account!=null)
|
||||
return vm.account.emojiHelper.getImageCount()+1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
SearchResultViewModel vm=data.get(position);
|
||||
if(vm.account!=null){
|
||||
if(image==0)
|
||||
return vm.account.avaRequest;
|
||||
return vm.account.emojiHelper.getImageRequest(image-1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomAccountViewHolder extends AccountViewHolder{
|
||||
public SearchResult searchResult;
|
||||
|
||||
public CustomAccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships){
|
||||
super(fragment, list, relationships);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
super.onClick();
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(searchResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||
|
||||
public TrendingHashtagsFragment(){
|
||||
super(10);
|
||||
@@ -54,13 +53,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
return new HashtagsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, .5f, 16, 16));
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.util.List;
|
||||
@@ -23,7 +27,8 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
onDataLoaded(List.of(
|
||||
new ListItem<>("Test email confirmation flow", null, this::onTestEmailConfirmClick),
|
||||
selfUpdateItem=new ListItem<>("Force self-update", null, this::onForceSelfUpdateClick),
|
||||
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick)
|
||||
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick),
|
||||
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick)
|
||||
));
|
||||
if(!GithubSelfUpdater.needSelfUpdating()){
|
||||
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
|
||||
@@ -55,6 +60,11 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
restartUI();
|
||||
}
|
||||
|
||||
private void onResetDiscoverBannersClick(){
|
||||
DiscoverInfoBannerHelper.reset();
|
||||
restartUI();
|
||||
}
|
||||
|
||||
private void restartUI(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
|
||||
@@ -90,7 +90,7 @@ public class SettingsServerFragment extends AppKitFragment{
|
||||
|
||||
tabBar=view.findViewById(R.id.tabbar);
|
||||
tabBar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||
tabBar.setTabTextSize(V.dp(16));
|
||||
tabBar.setTabTextSize(V.dp(14));
|
||||
tabLayoutMediator=new TabLayoutMediator(tabBar, pager, (tab, position)->tab.setText(switch(position){
|
||||
case 0 -> R.string.about_server;
|
||||
case 1 -> R.string.server_rules;
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
|
||||
public class SearchResult extends BaseModel implements DisplayItemsParent{
|
||||
public Account account;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.joinmastodon.android.model.Card;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class CardViewModel{
|
||||
public final Card card;
|
||||
public final ImageLoaderRequest imageRequest;
|
||||
|
||||
public CardViewModel(Card card, int width, int height){
|
||||
this.card=card;
|
||||
this.imageRequest=TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(width), V.dp(height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
|
||||
public class SearchResultViewModel{
|
||||
public SearchResult result;
|
||||
public AccountViewModel account;
|
||||
public ListItem<Hashtag> hashtagItem;
|
||||
|
||||
public SearchResultViewModel(SearchResult result, String accountID, boolean isRecents){
|
||||
this.result=result;
|
||||
switch(result.type){
|
||||
case ACCOUNT -> account=new AccountViewModel(result.account, accountID);
|
||||
case HASHTAG -> {
|
||||
hashtagItem=new ListItem<>((isRecents ? "#" : "")+result.hashtag.name, null, isRecents ? R.drawable.ic_history_24px : R.drawable.ic_tag_24px, null, result.hashtag);
|
||||
hashtagItem.isEnabled=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SearchViewHelper{
|
||||
private LinearLayout searchLayout;
|
||||
private EditText searchEdit;
|
||||
private ImageButton clearSearchButton;
|
||||
private View divider;
|
||||
private String currentQuery;
|
||||
private Consumer<String> listener;
|
||||
private Runnable debouncer=()->{
|
||||
currentQuery=searchEdit.getText().toString();
|
||||
if(listener!=null){
|
||||
listener.accept(currentQuery);
|
||||
}
|
||||
};
|
||||
private boolean isEmpty=true;
|
||||
private Runnable enterCallback;
|
||||
private Consumer<String> listenerWithoutDebounce;
|
||||
|
||||
public SearchViewHelper(Context context, Context toolbarContext, String hint){
|
||||
searchLayout=new LinearLayout(context);
|
||||
searchLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
searchEdit=new EditText(context);
|
||||
searchEdit.setHint(hint);
|
||||
searchEdit.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
|
||||
searchEdit.setBackground(null);
|
||||
searchEdit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
searchEdit.postDelayed(debouncer, 300);
|
||||
boolean newIsEmpty=e.length()==0;
|
||||
if(isEmpty!=newIsEmpty){
|
||||
isEmpty=newIsEmpty;
|
||||
V.setVisibilityAnimated(clearSearchButton, isEmpty ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
if(listenerWithoutDebounce!=null)
|
||||
listenerWithoutDebounce.accept(e.toString());
|
||||
}));
|
||||
searchEdit.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
|
||||
searchEdit.setOnEditorActionListener((v, actionId, event)->{
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
debouncer.run();
|
||||
if(enterCallback!=null)
|
||||
enterCallback.run();
|
||||
return true;
|
||||
});
|
||||
searchEdit.setTextAppearance(R.style.m3_body_large);
|
||||
searchEdit.setHintTextColor(UiUtils.getThemeColor(toolbarContext, R.attr.colorM3OnSurfaceVariant));
|
||||
searchEdit.setTextColor(UiUtils.getThemeColor(toolbarContext, R.attr.colorM3OnSurface));
|
||||
searchLayout.addView(searchEdit, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
clearSearchButton=new ImageButton(context);
|
||||
clearSearchButton.setImageResource(R.drawable.ic_baseline_close_24);
|
||||
clearSearchButton.setContentDescription(context.getString(R.string.clear));
|
||||
clearSearchButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
|
||||
clearSearchButton.setBackground(UiUtils.getThemeDrawable(toolbarContext, android.R.attr.actionBarItemBackground));
|
||||
clearSearchButton.setOnClickListener(v->{
|
||||
searchEdit.setText("");
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
debouncer.run();
|
||||
});
|
||||
clearSearchButton.setVisibility(View.INVISIBLE);
|
||||
searchLayout.addView(clearSearchButton, new LinearLayout.LayoutParams(V.dp(56), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
public void setListeners(Consumer<String> listener, Consumer<String> listenerWithoutDebounce){
|
||||
this.listener=listener;
|
||||
this.listenerWithoutDebounce=listenerWithoutDebounce;
|
||||
}
|
||||
|
||||
public void install(Toolbar toolbar){
|
||||
toolbar.getLayoutParams().height=V.dp(72);
|
||||
toolbar.setMinimumHeight(V.dp(72));
|
||||
if(searchLayout.getParent()!=null)
|
||||
((ViewGroup) searchLayout.getParent()).removeView(searchLayout);
|
||||
toolbar.addView(searchLayout, new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
toolbar.setBackgroundResource(R.drawable.bg_m3_surface3);
|
||||
searchEdit.requestFocus();
|
||||
}
|
||||
|
||||
public void addDivider(ViewGroup contentView){
|
||||
divider=new View(contentView.getContext());
|
||||
divider.setBackgroundColor(UiUtils.getThemeColor(contentView.getContext(), R.attr.colorM3Outline));
|
||||
contentView.addView(divider, 1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(1)));
|
||||
}
|
||||
|
||||
public LinearLayout getSearchLayout(){
|
||||
return searchLayout;
|
||||
}
|
||||
|
||||
public void setEnterCallback(Runnable enterCallback){
|
||||
this.enterCallback=enterCallback;
|
||||
}
|
||||
|
||||
public void setQuery(String q){
|
||||
currentQuery=q;
|
||||
searchEdit.setText(currentQuery);
|
||||
searchEdit.setSelection(searchEdit.length());
|
||||
searchEdit.removeCallbacks(debouncer);
|
||||
}
|
||||
|
||||
public String getQuery(){
|
||||
return currentQuery;
|
||||
}
|
||||
|
||||
public View getDivider(){
|
||||
return divider;
|
||||
}
|
||||
}
|
||||
@@ -4,61 +4,79 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
|
||||
public class DiscoverInfoBannerHelper{
|
||||
private View banner;
|
||||
private final BannerType type;
|
||||
private final String accountID;
|
||||
private static EnumSet<BannerType> bannerTypesToShow=EnumSet.noneOf(BannerType.class);
|
||||
|
||||
public DiscoverInfoBannerHelper(BannerType type){
|
||||
this.type=type;
|
||||
}
|
||||
|
||||
private SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("onboarding", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public void maybeAddBanner(FrameLayout view){
|
||||
if(!getPrefs().getBoolean("bannerHidden_"+type, false)){
|
||||
((Activity)view.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, view);
|
||||
banner=view.findViewById(R.id.discover_info_banner);
|
||||
view.findViewById(R.id.banner_dismiss).setOnClickListener(this::onDismissClick);
|
||||
TextView text=view.findViewById(R.id.banner_text);
|
||||
text.setText(switch(type){
|
||||
case TRENDING_POSTS -> R.string.trending_posts_info_banner;
|
||||
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
|
||||
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||
});
|
||||
static{
|
||||
for(BannerType t:BannerType.values()){
|
||||
if(!getPrefs().getBoolean("bannerHidden_"+t, false))
|
||||
bannerTypesToShow.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDismissClick(View v){
|
||||
if(banner==null)
|
||||
return;
|
||||
View _banner=banner;
|
||||
banner.animate()
|
||||
.alpha(0)
|
||||
.setDuration(200)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
.withEndAction(()->((ViewGroup)_banner.getParent()).removeView(_banner))
|
||||
.start();
|
||||
public DiscoverInfoBannerHelper(BannerType type, String accountID){
|
||||
this.type=type;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("onboarding", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public void maybeAddBanner(RecyclerView list, MergeRecyclerAdapter adapter){
|
||||
if(bannerTypesToShow.contains(type)){
|
||||
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
||||
TextView text=banner.findViewById(R.id.banner_text);
|
||||
text.setText(switch(type){
|
||||
case TRENDING_POSTS -> list.getResources().getString(R.string.trending_posts_info_banner);
|
||||
case TRENDING_LINKS -> list.getResources().getString(R.string.trending_links_info_banner);
|
||||
case LOCAL_TIMELINE -> list.getResources().getString(R.string.local_timeline_info_banner, AccountSessionManager.get(accountID).domain);
|
||||
case ACCOUNTS -> list.getResources().getString(R.string.recommended_accounts_info_banner);
|
||||
});
|
||||
ImageView icon=banner.findViewById(R.id.icon);
|
||||
icon.setImageResource(switch(type){
|
||||
case TRENDING_POSTS -> R.drawable.ic_whatshot_24px;
|
||||
case TRENDING_LINKS -> R.drawable.ic_feed_24px;
|
||||
case LOCAL_TIMELINE -> R.drawable.ic_stream_24px;
|
||||
case ACCOUNTS -> R.drawable.ic_group_add_24px;
|
||||
});
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
||||
}
|
||||
}
|
||||
|
||||
public void onBannerBecameVisible(){
|
||||
getPrefs().edit().putBoolean("bannerHidden_"+type, true).apply();
|
||||
banner=null;
|
||||
// bannerTypesToShow is not updated here on purpose so the banner keeps showing until the app is relaunched
|
||||
}
|
||||
|
||||
public static void reset(){
|
||||
SharedPreferences prefs=getPrefs();
|
||||
SharedPreferences.Editor e=prefs.edit();
|
||||
prefs.getAll().keySet().stream().filter(k->k.startsWith("bannerHidden_")).forEach(e::remove);
|
||||
e.apply();
|
||||
bannerTypesToShow=EnumSet.allOf(BannerType.class);
|
||||
}
|
||||
|
||||
public enum BannerType{
|
||||
TRENDING_POSTS,
|
||||
TRENDING_HASHTAGS,
|
||||
TRENDING_LINKS,
|
||||
LOCAL_TIMELINE,
|
||||
// ACCOUNTS
|
||||
ACCOUNTS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,8 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(float x, float y){
|
||||
if(relationships==null)
|
||||
return false;
|
||||
Relationship relationship=relationships.get(item.account.id);
|
||||
if(relationship==null)
|
||||
return false;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class HashtagChartView extends View implements CustomViewHelper{
|
||||
|
||||
public HashtagChartView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
paint.setStrokeWidth(dp(1.71f));
|
||||
paint.setStrokeWidth(dp(1));
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
}
|
||||
|
||||
9
mastodon/src/main/res/drawable/bg_rect_12dp_ripple.xml
Normal file
9
mastodon/src/main/res/drawable/bg_rect_12dp_ripple.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape>
|
||||
<solid android:color="#000"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
9
mastodon/src/main/res/drawable/ic_feed_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_feed_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H16L21,8V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V9H15V5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM7,17H17V15H7ZM7,9H12V7H7ZM7,13H17V11H7ZM5,5V9V5V9V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_group_add_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_group_add_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12.5,11.95Q13.225,11.15 13.613,10.125Q14,9.1 14,8Q14,6.9 13.613,5.875Q13.225,4.85 12.5,4.05Q14,4.25 15,5.375Q16,6.5 16,8Q16,9.5 15,10.625Q14,11.75 12.5,11.95ZM18,20V17Q18,16.1 17.6,15.288Q17.2,14.475 16.55,13.85Q17.825,14.3 18.913,15.012Q20,15.725 20,17V20ZM20,13V11H18V9H20V7H22V9H24V11H22V13ZM8,12Q6.35,12 5.175,10.825Q4,9.65 4,8Q4,6.35 5.175,5.175Q6.35,4 8,4Q9.65,4 10.825,5.175Q12,6.35 12,8Q12,9.65 10.825,10.825Q9.65,12 8,12ZM0,20V17.2Q0,16.35 0.438,15.637Q0.875,14.925 1.6,14.55Q3.15,13.775 4.75,13.387Q6.35,13 8,13Q9.65,13 11.25,13.387Q12.85,13.775 14.4,14.55Q15.125,14.925 15.562,15.637Q16,16.35 16,17.2V20ZM8,10Q8.825,10 9.413,9.412Q10,8.825 10,8Q10,7.175 9.413,6.588Q8.825,6 8,6Q7.175,6 6.588,6.588Q6,7.175 6,8Q6,8.825 6.588,9.412Q7.175,10 8,10ZM2,18H14V17.2Q14,16.925 13.863,16.7Q13.725,16.475 13.5,16.35Q12.15,15.675 10.775,15.337Q9.4,15 8,15Q6.6,15 5.225,15.337Q3.85,15.675 2.5,16.35Q2.275,16.475 2.138,16.7Q2,16.925 2,17.2ZM8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8Q8,8 8,8ZM8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Q8,18 8,18Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_history_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_history_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M14.8,16.2 L11,12.4V7H13V11.6L16.2,14.8ZM12,21Q8.55,21 5.988,18.712Q3.425,16.425 3.05,13H5.1Q5.45,15.6 7.412,17.3Q9.375,19 12,19Q14.925,19 16.962,16.962Q19,14.925 19,12Q19,9.075 16.962,7.037Q14.925,5 12,5Q10.275,5 8.775,5.8Q7.275,6.6 6.25,8H9V10H3V4H5V6.35Q6.275,4.75 8.113,3.875Q9.95,3 12,3Q13.875,3 15.513,3.712Q17.15,4.425 18.363,5.637Q19.575,6.85 20.288,8.487Q21,10.125 21,12Q21,13.875 20.288,15.512Q19.575,17.15 18.363,18.362Q17.15,19.575 15.513,20.288Q13.875,21 12,21Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_link_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_link_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,17H7Q4.925,17 3.463,15.537Q2,14.075 2,12Q2,9.925 3.463,8.462Q4.925,7 7,7H11V9H7Q5.75,9 4.875,9.875Q4,10.75 4,12Q4,13.25 4.875,14.125Q5.75,15 7,15H11ZM8,13V11H16V13ZM13,17V15H17Q18.25,15 19.125,14.125Q20,13.25 20,12Q20,10.75 19.125,9.875Q18.25,9 17,9H13V7H17Q19.075,7 20.538,8.462Q22,9.925 22,12Q22,14.075 20.538,15.537Q19.075,17 17,17Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_person_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_person_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12Q10.35,12 9.175,10.825Q8,9.65 8,8Q8,6.35 9.175,5.175Q10.35,4 12,4Q13.65,4 14.825,5.175Q16,6.35 16,8Q16,9.65 14.825,10.825Q13.65,12 12,12ZM4,20V17.2Q4,16.35 4.438,15.637Q4.875,14.925 5.6,14.55Q7.15,13.775 8.75,13.387Q10.35,13 12,13Q13.65,13 15.25,13.387Q16.85,13.775 18.4,14.55Q19.125,14.925 19.562,15.637Q20,16.35 20,17.2V20ZM6,18H18V17.2Q18,16.925 17.863,16.7Q17.725,16.475 17.5,16.35Q16.15,15.675 14.775,15.337Q13.4,15 12,15Q10.6,15 9.225,15.337Q7.85,15.675 6.5,16.35Q6.275,16.475 6.138,16.7Q6,16.925 6,17.2ZM12,10Q12.825,10 13.413,9.412Q14,8.825 14,8Q14,7.175 13.413,6.588Q12.825,6 12,6Q11.175,6 10.588,6.588Q10,7.175 10,8Q10,8.825 10.588,9.412Q11.175,10 12,10ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Q12,18 12,18Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_stream_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_stream_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,14Q3.175,14 2.588,13.412Q2,12.825 2,12Q2,11.175 2.588,10.587Q3.175,10 4,10Q4.825,10 5.412,10.587Q6,11.175 6,12Q6,12.825 5.412,13.412Q4.825,14 4,14ZM5.65,19.7 L4.25,18.3 8.6,13.95 10,15.35ZM8.65,10 L4.3,5.65 5.7,4.25 10.05,8.6ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20Q10,19.175 10.588,18.587Q11.175,18 12,18Q12.825,18 13.413,18.587Q14,19.175 14,20Q14,20.825 13.413,21.413Q12.825,22 12,22ZM12,6Q11.175,6 10.588,5.412Q10,4.825 10,4Q10,3.175 10.588,2.587Q11.175,2 12,2Q12.825,2 13.413,2.587Q14,3.175 14,4Q14,4.825 13.413,5.412Q12.825,6 12,6ZM15.35,10.05 L13.95,8.6 18.35,4.25 19.75,5.65ZM18.35,19.7 L14,15.35 15.4,13.95 19.75,18.3ZM20,14Q19.175,14 18.587,13.412Q18,12.825 18,12Q18,11.175 18.587,10.587Q19.175,10 20,10Q20.825,10 21.413,10.587Q22,11.175 22,12Q22,12.825 21.413,13.412Q20.825,14 20,14Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_tag_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_tag_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,20 L7,16H3.5L4,14H7.5L8.5,10H4.5L5,8H9L10,4H12L11,8H15L16,4H18L17,8H20.5L20,10H16.5L15.5,14H19.5L19,16H15L14,20H12L13,16H9L8,20ZM9.5,14H13.5L14.5,10H10.5Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_whatshot_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_whatshot_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,22Q10.35,22 8.812,21.488Q7.275,20.975 6,20L7.45,18.55Q8.5,19.275 9.65,19.637Q10.8,20 12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12H2Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.887,2.787Q17.7,3.575 19.062,4.938Q20.425,6.3 21.212,8.113Q22,9.925 22,12Q22,14.05 21.212,15.875Q20.425,17.7 19.062,19.062Q17.7,20.425 15.887,21.212Q14.075,22 12,22ZM3.975,17.925 L8.05,13.85 11.05,16.35 16,11.4V14H18V8H12V10H14.6L10.95,13.65L7.95,11.15L2.925,16.175Q3.2,16.75 3.413,17.113Q3.625,17.475 3.975,17.925ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorSearchField"/>
|
||||
<corners android:radius="6dp"/>
|
||||
<corners android:radius="12dp"/>
|
||||
<solid android:color="#000"/>
|
||||
</shape>
|
||||
@@ -5,30 +5,27 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:elevation="1dp"
|
||||
android:outlineProvider="background"
|
||||
android:background="?colorWindowBackground">
|
||||
android:layout_margin="16dp"
|
||||
android:padding="16dp"
|
||||
android:background="@drawable/rect_12dp"
|
||||
android:backgroundTint="?colorM3SurfaceVariant">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:tint="?colorM3OnPrimaryContainer"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@drawable/ic_whatshot_24px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/banner_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="@string/trending_posts_info_banner"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/banner_dismiss"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="8dp"
|
||||
android:src="@drawable/ic_fluent_dismiss_circle_24_filled"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/dismiss"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -9,106 +9,74 @@
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?android:statusBarColor">
|
||||
|
||||
<!-- https://github.com/mastodon/mastodon-android/issues/95 -->
|
||||
<View
|
||||
android:layout_width="1px"
|
||||
android:layout_height="1px"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"/>
|
||||
|
||||
<FrameLayout
|
||||
android:background="?colorM3Surface">
|
||||
<LinearLayout
|
||||
android:id="@+id/search_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_search_field"
|
||||
android:outlineProvider="background"
|
||||
android:clipToOutline="true">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:hint="@string/search_hint"
|
||||
android:textColorHint="?colorSearchHint"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="16dp"
|
||||
android:singleLine="true"
|
||||
android:inputType="textFilter"
|
||||
android:imeOptions="actionSearch"
|
||||
android:paddingLeft="48dp"
|
||||
android:paddingRight="48dp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"/>
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/bg_m3_surface3">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/search_back"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="?colorSearchHint"
|
||||
android:elevation="1dp"
|
||||
android:layout_margin="8dp"
|
||||
android:contentDescription="@string/back"
|
||||
android:src="@drawable/ic_fluent_search_24_regular"/>
|
||||
android:background="@drawable/bg_round_ripple"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_search_24px"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/search_clear"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="?colorSearchHint"
|
||||
android:elevation="1dp"
|
||||
android:visibility="invisible"
|
||||
android:contentDescription="@string/clear"
|
||||
android:src="@drawable/ic_fluent_dismiss_24_regular"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/search_progress"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="14dp"
|
||||
android:indeterminateTint="?colorSearchHint"
|
||||
style="?android:progressBarStyleSmall"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/search_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:text="@string/search_mastodon"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
<LinearLayout
|
||||
android:id="@+id/discover_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:tabGravity="start"
|
||||
app:tabMinWidth="120dp"
|
||||
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?android:textColorPrimary"
|
||||
app:tabMode="scrollable"
|
||||
android:background="@drawable/bg_discover_tabs"/>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:tabGravity="start"
|
||||
app:tabIndicator="@drawable/tab_indicator_m3"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?colorM3Primary"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabMinWidth="90dp"
|
||||
app:tabMode="scrollable"
|
||||
android:background="?colorM3Surface"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
<View
|
||||
android:id="@+id/tabs_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?colorM3SurfaceVariant"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/search_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"/>
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/search_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
||||
@@ -8,18 +8,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="?android:windowBackground">
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:tabGravity="start"
|
||||
app:tabMinWidth="120dp"
|
||||
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?android:textColorPrimary"
|
||||
app:tabMode="scrollable"
|
||||
android:background="@drawable/bg_discover_tabs"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/appkit_loader_content"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -3,28 +3,31 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
android:paddingVertical="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="132dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignBottom="@id/subtitle"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:layout_height="16dp"
|
||||
android:layout_toEndOf="@id/photo"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Site Name"/>
|
||||
|
||||
<TextView
|
||||
@@ -32,24 +35,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:maxLines="5"
|
||||
android:layout_toEndOf="@id/photo"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:paddingVertical="2.5dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Title title title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Discussed 123 times"/>
|
||||
|
||||
</RelativeLayout>
|
||||
44
mastodon/src/main/res/layout/item_trending_link_card.xml
Normal file
44
mastodon/src/main/res/layout/item_trending_link_card.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="240dp"
|
||||
android:foreground="@drawable/bg_settings_banner">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="140dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/photo"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:paddingVertical="2.5dp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Title title title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Site Name"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -22,6 +22,7 @@
|
||||
<item name="list_item_switch" type="id"/>
|
||||
<item name="list_item_checkbox" type="id"/>
|
||||
<item name="list_item_radio" type="id"/>
|
||||
<item name="list_item_account" type="id"/>
|
||||
|
||||
<item name="server_about" type="id"/>
|
||||
<item name="server_rules" type="id"/>
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
<string name="visibility_private">Only people mentioned</string>
|
||||
<string name="search_all">All</string>
|
||||
<string name="search_people">People</string>
|
||||
<string name="recent_searches">Recent searches</string>
|
||||
<string name="recent_searches">Recents</string>
|
||||
<string name="step_x_of_n">Step %1$d of %2$d</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="notification_type_follow">New followers</string>
|
||||
@@ -307,11 +307,12 @@
|
||||
<string name="file_saved">File saved</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="no_app_to_handle_action">There’s no app to handle this action</string>
|
||||
<string name="local_timeline">Community</string>
|
||||
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_links_info_banner">These are the news stories being shared the most in your corner of Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">These are the most recent posts by the people who use the same Mastodon server as you.</string>
|
||||
<string name="local_timeline">Local</string>
|
||||
<string name="trending_posts_info_banner">These are the posts gaining traction across Mastodon.</string>
|
||||
<string name="trending_links_info_banner">These are the news stories getting talked about on Mastodon.</string>
|
||||
<!-- %s is the server domain -->
|
||||
<string name="local_timeline_info_banner">These are all the posts from all users in your server (%s).</string>
|
||||
<string name="recommended_accounts_info_banner">You might like these accounts based on others you follow.</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="see_new_posts">See new posts</string>
|
||||
<string name="load_missing_posts">Load missing posts</string>
|
||||
@@ -633,4 +634,11 @@
|
||||
<string name="downloading_update">Downloading (%d%%)</string>
|
||||
<!-- Shown like a content warning, %s is the name of the filter -->
|
||||
<string name="post_matches_filter_x">Matches filter “%s”</string>
|
||||
<string name="search_mastodon">Search Mastodon</string>
|
||||
<string name="clear_all">Clear all</string>
|
||||
<string name="search_open_url">Open URL in Mastodon</string>
|
||||
<string name="posts_matching_hashtag">Posts with “%s”</string>
|
||||
<string name="search_go_to_account">Go to %s</string>
|
||||
<string name="posts_matching_string">Posts with “%s”</string>
|
||||
<string name="accounts_matching_string">People with “%s”</string>
|
||||
</resources>
|
||||
@@ -440,6 +440,7 @@
|
||||
<item name="android:textSize">16dp</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:lineSpacingExtra">5dp</item>
|
||||
<item name="android:lineHeight">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_body_medium">
|
||||
@@ -479,6 +480,7 @@
|
||||
<item name="android:textSize">12dp</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:lineSpacingMultiplier">1.14</item>
|
||||
<item name="android:lineHeight">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_label_large">
|
||||
|
||||
Reference in New Issue
Block a user