M3 redesign: search/discover
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user