M3 redesign: search/discover

This commit is contained in:
Grishka
2023-06-24 22:56:55 +03:00
parent c9e467ac2f
commit e1db5f15ca
40 changed files with 1300 additions and 774 deletions

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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);
}