diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
index 0cbf07143..2d6ca8d1f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
@@ -28,6 +28,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
@@ -52,6 +53,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
+import org.joinmastodon.android.fragments.account_list.ComposeAccountSearchFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
@@ -96,6 +98,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private static final int MEDIA_RESULT=717;
public static final int IMAGE_DESCRIPTION_RESULT=363;
+ private static final int AUTOCOMPLETE_ACCOUNT_RESULT=779;
private static final String TAG="ComposeFragment";
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
@@ -262,6 +265,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
public void onIconChanged(int icon){
emojiBtn.setSelected(icon!=PopupKeyboard.ICON_HIDDEN);
+ if(autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){
+ contentView.layout(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());
+ if(icon==PopupKeyboard.ICON_HIDDEN)
+ showAutocomplete();
+ else
+ hideAutocomplete();
+ }
}
});
@@ -294,7 +304,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updateVisibilityIcon();
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
- autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
+ autocompleteViewController.setCompletionSelectedListener(new ComposeAutocompleteViewController.AutocompleteListener(){
+ @Override
+ public void onCompletionSelected(String completion){
+ onAutocompleteOptionSelected(completion);
+ }
+
+ @Override
+ public void onSetEmojiPanelOpen(boolean open){
+ if(open!=emojiKeyboard.isVisible())
+ emojiKeyboard.toggleKeyboardPopup(mainEditText);
+ }
+
+ @Override
+ public void onLaunchAccountSearch(){
+ Bundle args=new Bundle();
+ args.putString("account", accountID);
+ Nav.goForResult(getActivity(), ComposeAccountSearchFragment.class, args, AUTOCOMPLETE_ACCOUNT_RESULT, ComposeFragment.this);
+ }
+ });
View autocompleteView=autocompleteViewController.getView();
autocompleteView.setVisibility(View.INVISIBLE);
bottomBar.addView(autocompleteView, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(56)));
@@ -315,6 +343,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
mediaViewController.onSaveInstanceState(outState);
outState.putBoolean("hasSpoiler", hasSpoiler);
outState.putSerializable("visibility", statusVisibility);
+ if(currentAutocompleteSpan!=null){
+ Editable e=mainEditText.getText();
+ outState.putInt("autocompleteStart", e.getSpanStart(currentAutocompleteSpan));
+ outState.putInt("autocompleteEnd", e.getSpanEnd(currentAutocompleteSpan));
+ }
}
@Override
@@ -471,6 +504,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updateMediaPollStates();
}
+ @Override
+ public void onViewStateRestored(Bundle savedInstanceState){
+ super.onViewStateRestored(savedInstanceState);
+ if(savedInstanceState!=null && savedInstanceState.containsKey("autocompleteStart")){
+ int start=savedInstanceState.getInt("autocompleteStart"), end=savedInstanceState.getInt("autocompleteEnd");
+ currentAutocompleteSpan=new ComposeAutocompleteSpan();
+ mainEditText.getText().setSpan(currentAutocompleteSpan, start, end, Editable.SPAN_EXCLUSIVE_INCLUSIVE);
+ startAutocomplete(currentAutocompleteSpan);
+ }
+ }
+
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
@@ -537,6 +581,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private void onCustomEmojiClick(Emoji emoji){
if(getActivity().getCurrentFocus() instanceof EditText edit){
+ if(edit==mainEditText && currentAutocompleteSpan!=null && autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){
+ Editable text=mainEditText.getText();
+ int start=text.getSpanStart(currentAutocompleteSpan);
+ int end=text.getSpanEnd(currentAutocompleteSpan);
+ finishAutocomplete();
+ text.replace(start, end, ':'+emoji.shortcode+':');
+ return;
+ }
int start=edit.getSelectionStart();
String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":";
edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':');
@@ -549,6 +601,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Background, R.attr.colorM3Primary, 0.11f);
getToolbar().setBackgroundColor(color);
setStatusBarColor(color);
+ setNavigationBarColor(color);
bottomBar.setBackgroundColor(color);
}
@@ -694,6 +747,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String attID=result.getString("attachment");
String text=result.getString("text");
mediaViewController.setAltTextByID(attID, text);
+ }else if(reqCode==AUTOCOMPLETE_ACCOUNT_RESULT && success){
+ Account acc=Parcels.unwrap(result.getParcelable("selectedAccount"));
+ if(currentAutocompleteSpan==null)
+ return;
+ Editable e=mainEditText.getText();
+ int start=e.getSpanStart(currentAutocompleteSpan);
+ int end=e.getSpanEnd(currentAutocompleteSpan);
+ e.removeSpan(currentAutocompleteSpan);
+ e.replace(start, end, '@'+acc.acct+' ');
+ finishAutocomplete();
}
}
@@ -905,6 +968,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Editable e=mainEditText.getText();
String spanText=e.toString().substring(e.getSpanStart(span), e.getSpanEnd(span));
autocompleteViewController.setText(spanText);
+ showAutocomplete();
+ }
+
+ private void finishAutocomplete(){
+ if(currentAutocompleteSpan==null)
+ return;
+ autocompleteViewController.setText(null);
+ currentAutocompleteSpan=null;
+ hideAutocomplete();
+ }
+
+ private void showAutocomplete(){
UiUtils.beginLayoutTransition(bottomBar);
View autocompleteView=autocompleteViewController.getView();
bottomBar.getLayoutParams().height=ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -913,11 +988,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
autocompleteDivider.setVisibility(View.VISIBLE);
}
- private void finishAutocomplete(){
- if(currentAutocompleteSpan==null)
- return;
- autocompleteViewController.setText(null);
- currentAutocompleteSpan=null;
+ private void hideAutocomplete(){
UiUtils.beginLayoutTransition(bottomBar);
bottomBar.getLayoutParams().height=V.dp(48);
bottomBar.requestLayout();
@@ -930,8 +1001,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
int start=e.getSpanStart(currentAutocompleteSpan);
int end=e.getSpanEnd(currentAutocompleteSpan);
e.replace(start, end, text+" ");
- mainEditText.setSelection(start+text.length()+1);
finishAutocomplete();
+ InputConnection conn=mainEditText.getCurrentInputConnection();
+ if(conn!=null)
+ conn.finishComposingText();
}
@Override
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
index 9c39219c1..fb4ca4a52 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
@@ -136,6 +136,8 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment=29 && insets.getTappableElementInsets().bottom==0){
list.setPadding(0, V.dp(16), 0, V.dp(16)+insets.getSystemWindowInsetBottom());
+ emptyView.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
+ progress.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
}else{
list.setPadding(0, V.dp(16), 0, V.dp(16));
@@ -143,6 +145,8 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
super(imgLoader);
@@ -151,7 +155,9 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment{
+ currentQuery=searchEdit.getText().toString();
+ if(currentRequest!=null){
+ currentRequest.cancel();
+ currentRequest=null;
+ }
+ if(!TextUtils.isEmpty(currentQuery))
+ loadData();
+ };
+ private boolean resultDelivered;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+ setRefreshEnabled(false);
+ setEmptyText("");
+ dataLoaded();
+ }
+
+ @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));
+
+ 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);
+ }
+
+ @Override
+ protected void doLoadData(int offset, int count){
+ refreshing=true;
+ currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
+ .setCallback(new SimpleCallback<>(this){
+ @Override
+ public void onSuccess(SearchResults result){
+ setEmptyText(R.string.no_search_results);
+ onDataLoaded(result.accounts.stream().map(AccountViewModel::new).collect(Collectors.toList()), false);
+ }
+ })
+ .exec(accountID);
+ }
+
+ @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();
+ }
+
+ @Override
+ protected boolean wantsElevationOnScrollEffect(){
+ return false;
+ }
+
+ @Override
+ protected void onConfigureViewHolder(AccountViewHolder holder){
+ super.onConfigureViewHolder(holder);
+ holder.setOnClickListener(this::onItemClick);
+ }
+
+ private void onItemClick(AccountViewHolder holder){
+ if(resultDelivered)
+ return;
+
+ resultDelivered=true;
+ Bundle res=new Bundle();
+ res.putParcelable("selectedAccount", Parcels.wrap(holder.getItem().account));
+ setResult(true, res);
+ Nav.finish(this, false);
+ }
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
index d89a79dbe..b07679282 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
@@ -733,4 +733,11 @@ public class UiUtils{
.setInterpolator(CubicBezierInterpolator.DEFAULT)
);
}
+
+ public static Drawable getThemeDrawable(Context context, @AttrRes int attr){
+ TypedArray ta=context.obtainStyledAttributes(new int[]{attr});
+ Drawable d=ta.getDrawable(0);
+ ta.recycle();
+ return d;
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java
index c487c3a06..4d89e0e05 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeAutocompleteViewController.java
@@ -3,13 +3,13 @@ package org.joinmastodon.android.ui.viewcontrollers;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.ProgressBar;
+import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
@@ -23,11 +23,13 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
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.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.joinmastodon.android.ui.views.FilterChipView;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -44,16 +46,18 @@ 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.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class ComposeAutocompleteViewController{
+ private static final int LOADING_FAKE_USER_COUNT=3;
+
private Activity activity;
private String accountID;
private FrameLayout contentView;
private UsableRecyclerView list;
private ListImageLoaderWrapper imgLoader;
- private ProgressBar progress;
private List users=Collections.emptyList();
private List hashtags=Collections.emptyList();
private List emojis=Collections.emptyList();
@@ -61,13 +65,17 @@ public class ComposeAutocompleteViewController{
private APIRequest currentRequest;
private Runnable usersDebouncer=this::doSearchUsers, hashtagsDebouncer=this::doSearchHashtags;
private String lastText;
- private boolean listIsHidden=true;
+ private boolean isLoading;
+ private FilterChipView emptyButton;
+ private HideableSingleViewRecyclerAdapter emptyButtonAdapter;
private UsersAdapter usersAdapter;
private HashtagsAdapter hashtagsAdapter;
private EmojisAdapter emojisAdapter;
+ private MergeRecyclerAdapter usersMergeAdapter;
+ private MergeRecyclerAdapter emojisMergeAdapter;
- private Consumer completionSelectedListener;
+ private AutocompleteListener completionSelectedListener;
public ComposeAutocompleteViewController(Activity activity, String accountID){
this.activity=activity;
@@ -77,7 +85,6 @@ public class ComposeAutocompleteViewController{
list=new UsableRecyclerView(activity);
list.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));
list.setItemAnimator(new BetterItemAnimator());
- list.setVisibility(View.GONE);
list.setPadding(V.dp(16), V.dp(12), V.dp(16), V.dp(12));
list.setClipToPadding(false);
list.setSelector(null);
@@ -90,10 +97,15 @@ public class ComposeAutocompleteViewController{
});
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- progress=new ProgressBar(activity);
- FrameLayout.LayoutParams progressLP=new FrameLayout.LayoutParams(V.dp(48), V.dp(48), Gravity.CENTER_HORIZONTAL|Gravity.TOP);
- progressLP.topMargin=V.dp(16);
- contentView.addView(progress, progressLP);
+ emptyButton=new FilterChipView(activity);
+ emptyButtonAdapter=new HideableSingleViewRecyclerAdapter(emptyButton);
+ emptyButton.setOnClickListener(v->{
+ if(mode==Mode.EMOJIS){
+ completionSelectedListener.onSetEmojiPanelOpen(true);
+ }else if(mode==Mode.USERS){
+ completionSelectedListener.onLaunchAccountSearch();
+ }
+ });
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
}
@@ -104,13 +116,15 @@ public class ComposeAutocompleteViewController{
}else if(mode==Mode.HASHTAGS){
list.removeCallbacks(hashtagsDebouncer);
}
- if(text==null)
- return;
- Mode prevMode=mode;
if(currentRequest!=null){
currentRequest.cancel();
currentRequest=null;
}
+ if(text==null){
+ reset();
+ return;
+ }
+ Mode prevMode=mode;
mode=switch(text.charAt(0)){
case '@' -> Mode.USERS;
case '#' -> Mode.HASHTAGS;
@@ -118,16 +132,33 @@ public class ComposeAutocompleteViewController{
default -> throw new IllegalStateException("Unexpected value: "+text.charAt(0));
};
if(prevMode!=mode){
+ if(mode==Mode.USERS){
+ isLoading=true;
+ emptyButtonAdapter.setVisible(false);
+ }
+
list.setAdapter(switch(mode){
case USERS -> {
- if(usersAdapter==null)
+ if(usersAdapter==null){
usersAdapter=new UsersAdapter();
- yield usersAdapter;
+ usersMergeAdapter=new MergeRecyclerAdapter();
+ usersMergeAdapter.addAdapter(emptyButtonAdapter);
+ usersMergeAdapter.addAdapter(usersAdapter);
+ }
+ emptyButton.setText(R.string.compose_autocomplete_users_empty);
+ emptyButton.setDrawableStartTinted(R.drawable.ic_search_20px);
+ yield usersMergeAdapter;
}
case EMOJIS -> {
- if(emojisAdapter==null)
+ if(emojisAdapter==null){
emojisAdapter=new EmojisAdapter();
- yield emojisAdapter;
+ emojisMergeAdapter=new MergeRecyclerAdapter();
+ emojisMergeAdapter.addAdapter(emptyButtonAdapter);
+ emojisMergeAdapter.addAdapter(emojisAdapter);
+ }
+ emptyButton.setText(R.string.compose_autocomplete_emoji_empty);
+ emptyButton.setDrawableStartTinted(R.drawable.ic_mood_20px);
+ yield emojisMergeAdapter;
}
case HASHTAGS -> {
if(hashtagsAdapter==null)
@@ -135,20 +166,18 @@ public class ComposeAutocompleteViewController{
yield hashtagsAdapter;
}
});
- if(mode!=Mode.EMOJIS){
- list.setVisibility(View.GONE);
- progress.setVisibility(View.VISIBLE);
- listIsHidden=true;
- }else if(listIsHidden){
- list.setVisibility(View.VISIBLE);
- progress.setVisibility(View.GONE);
- listIsHidden=false;
- }
}
lastText=text;
if(mode==Mode.USERS){
list.postDelayed(usersDebouncer, 300);
}else if(mode==Mode.HASHTAGS){
+ List oldList=hashtags;
+ hashtags=new ArrayList<>();
+ Hashtag tag=new Hashtag();
+ tag.name=lastText.substring(1);
+ hashtags.add(tag);
+ UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name));
+
list.postDelayed(hashtagsDebouncer, 300);
}else if(mode==Mode.EMOJIS){
String _text=text.substring(1); // remove ':'
@@ -165,12 +194,14 @@ public class ComposeAutocompleteViewController{
.filter(e -> e.shortcode.toLowerCase().contains(_text.toLowerCase())))
.map(WrappedEmoji::new)
.collect(Collectors.toList());
+ emptyButtonAdapter.setVisible(emojis.isEmpty());
UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode));
+ list.invalidateItemDecorations();
imgLoader.updateImages();
}
}
- public void setCompletionSelectedListener(Consumer completionSelectedListener){
+ public void setCompletionSelectedListener(AutocompleteListener completionSelectedListener){
this.completionSelectedListener=completionSelectedListener;
}
@@ -178,6 +209,17 @@ public class ComposeAutocompleteViewController{
return contentView;
}
+ public void reset(){
+ mode=null;
+ users.clear();
+ emojis.clear();
+ hashtags.clear();
+ }
+
+ public Mode getMode(){
+ return mode;
+ }
+
private void doSearchUsers(){
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false)
.setCallback(new Callback<>(){
@@ -186,13 +228,22 @@ public class ComposeAutocompleteViewController{
currentRequest=null;
List oldList=users;
users=result.accounts.stream().map(WrappedAccount::new).collect(Collectors.toList());
- UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id));
- imgLoader.updateImages();
- if(listIsHidden){
- listIsHidden=false;
- V.setVisibilityAnimated(list, View.VISIBLE);
- V.setVisibilityAnimated(progress, View.GONE);
+ if(isLoading){
+ isLoading=false;
+ if(users.size()>=LOADING_FAKE_USER_COUNT){
+ usersAdapter.notifyItemRangeChanged(0, LOADING_FAKE_USER_COUNT);
+ if(users.size()>LOADING_FAKE_USER_COUNT)
+ usersAdapter.notifyItemRangeInserted(LOADING_FAKE_USER_COUNT, users.size()-LOADING_FAKE_USER_COUNT);
+ }else{
+ usersAdapter.notifyItemRangeChanged(0, users.size());
+ usersAdapter.notifyItemRangeRemoved(users.size(), LOADING_FAKE_USER_COUNT-users.size());
+ }
+ }else{
+ UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id));
}
+ list.invalidateItemDecorations();
+ emptyButtonAdapter.setVisible(users.isEmpty());
+ imgLoader.updateImages();
}
@Override
@@ -209,15 +260,12 @@ public class ComposeAutocompleteViewController{
@Override
public void onSuccess(SearchResults result){
currentRequest=null;
+ if(result.hashtags.isEmpty() || (result.hashtags.size()==1 && result.hashtags.get(0).name.equals(lastText.substring(1))))
+ return;
List oldList=hashtags;
hashtags=result.hashtags;
UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name));
- imgLoader.updateImages();
- if(listIsHidden){
- listIsHidden=false;
- V.setVisibilityAnimated(list, View.VISIBLE);
- V.setVisibilityAnimated(progress, View.GONE);
- }
+ list.invalidateItemDecorations();
}
@Override
@@ -236,23 +284,31 @@ public class ComposeAutocompleteViewController{
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
- return new UserViewHolder();
+ return switch(viewType){
+ case 0 -> new UserViewHolder();
+ case 1 -> new LoadingUserViewHolder();
+ default -> throw new IllegalStateException("Unexpected value: "+viewType);
+ };
}
@Override
public int getItemCount(){
+ if(isLoading)
+ return LOADING_FAKE_USER_COUNT;
return users.size();
}
@Override
public void onBindViewHolder(UserViewHolder holder, int position){
- holder.bind(users.get(position));
- super.onBindViewHolder(holder, position);
+ if(!isLoading){
+ holder.bind(users.get(position));
+ super.onBindViewHolder(holder, position);
+ }
}
@Override
public int getImageCountForItem(int position){
- return 1/*+users.get(position).emojiHelper.getImageCount()*/;
+ return isLoading ? 0 : 1;
}
@Override
@@ -262,13 +318,18 @@ public class ComposeAutocompleteViewController{
return a.avaRequest;
return a.emojiHelper.getImageRequest(image-1);
}
+
+ @Override
+ public int getItemViewType(int position){
+ return isLoading ? 1 : 0;
+ }
}
private class UserViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
- private final ImageView ava;
- private final TextView username;
+ protected final ImageView ava;
+ protected final TextView username;
- private UserViewHolder(){
+ public UserViewHolder(){
super(activity, R.layout.item_autocomplete_user, list);
ava=findViewById(R.id.photo);
username=findViewById(R.id.username);
@@ -283,7 +344,7 @@ public class ComposeAutocompleteViewController{
@Override
public void onClick(){
- completionSelectedListener.accept("@"+item.account.acct);
+ completionSelectedListener.onCompletionSelected("@"+item.account.acct);
}
@Override
@@ -297,7 +358,24 @@ public class ComposeAutocompleteViewController{
@Override
public void clearImage(int index){
- setImage(index, null);
+ if(index==0)
+ ava.setImageResource(R.drawable.image_placeholder);
+ else
+ setImage(index, null);
+ }
+ }
+
+ private class LoadingUserViewHolder extends UserViewHolder implements UsableRecyclerView.DisableableClickable{
+ public LoadingUserViewHolder(){
+ int color=UiUtils.getThemeColor(activity, R.attr.colorM3OutlineVariant);
+ ava.setImageDrawable(new ColorDrawable(color));
+ username.setLayoutParams(new LinearLayout.LayoutParams(V.dp(64), V.dp(10)));
+ username.setBackgroundColor(color);
+ }
+
+ @Override
+ public boolean isEnabled(){
+ return false;
}
}
@@ -336,7 +414,7 @@ public class ComposeAutocompleteViewController{
@Override
public void onClick(){
- completionSelectedListener.accept("#"+item.name);
+ completionSelectedListener.onCompletionSelected("#"+item.name);
}
}
@@ -401,7 +479,7 @@ public class ComposeAutocompleteViewController{
@Override
public void onClick(){
- completionSelectedListener.accept(":"+item.emoji.shortcode+":");
+ completionSelectedListener.onCompletionSelected(":"+item.emoji.shortcode+":");
}
}
@@ -430,9 +508,15 @@ public class ComposeAutocompleteViewController{
}
}
- private enum Mode{
+ public enum Mode{
USERS,
HASHTAGS,
EMOJIS
}
+
+ public interface AutocompleteListener{
+ void onCompletionSelected(String completion);
+ void onSetEmojiPanelOpen(boolean open);
+ void onLaunchAccountSearch();
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
index f8901178b..08ddff4e4 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
@@ -36,6 +36,7 @@ import org.parceler.Parcels;
import java.util.HashMap;
import java.util.Objects;
+import java.util.function.Consumer;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -56,6 +57,8 @@ public class AccountViewHolder extends BindableViewHolder impl
private final Fragment fragment;
private final HashMap relationships;
+ private Consumer onClick;
+
public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap relationships){
super(fragment.getActivity(), R.layout.item_account_list, list);
this.fragment=fragment;
@@ -140,6 +143,10 @@ public class AccountViewHolder extends BindableViewHolder impl
@Override
public void onClick(){
+ if(onClick!=null){
+ onClick.accept(this);
+ return;
+ }
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(item.account));
@@ -253,4 +260,8 @@ public class AccountViewHolder extends BindableViewHolder impl
relationships.put(item.account.id, r);
bindRelationship();
}
+
+ public void setOnClickListener(Consumer listener){
+ onClick=listener;
+ }
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ComposeEditText.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ComposeEditText.java
index 0d03085bf..9612f9a4d 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ComposeEditText.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ComposeEditText.java
@@ -21,6 +21,7 @@ import androidx.annotation.RequiresApi;
public class ComposeEditText extends EditText{
private SelectionListener selectionListener;
+ private InputConnection currentInputConnection;
public ComposeEditText(Context context){
super(context);
@@ -49,15 +50,19 @@ public class ComposeEditText extends EditText{
this.selectionListener=selectionListener;
}
+ public InputConnection getCurrentInputConnection(){
+ return currentInputConnection;
+ }
+
// Support receiving images from keyboards
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
final InputConnection ic=super.onCreateInputConnection(outAttrs);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
- return new MediaAcceptingInputConnection(ic);
+ return currentInputConnection=new MediaAcceptingInputConnection(ic);
}
- return ic;
+ return currentInputConnection=ic;
}
// Support pasting images
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java
index 560388267..82bb71b4b 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java
@@ -1,7 +1,6 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.Button;
@@ -30,7 +29,6 @@ public class FilterChipView extends Button{
setBackgroundResource(R.drawable.bg_filter_chip);
setTextAppearance(R.style.m3_label_large);
setTextColor(getResources().getColorStateList(R.color.filter_chip_text, context.getTheme()));
- setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface)));
updatePadding();
}
@@ -40,7 +38,9 @@ public class FilterChipView extends Button{
if(currentlySelected==isSelected())
return;
currentlySelected=isSelected();
- Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()) : null;
+ Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()).mutate() : null;
+ if(start!=null)
+ start.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface));
Drawable end=getCompoundDrawablesRelative()[2];
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
updatePadding();
@@ -53,7 +53,18 @@ public class FilterChipView extends Button{
}
public void setDrawableEnd(@DrawableRes int drawable){
- setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, drawable, 0);
+ Drawable icon=getResources().getDrawable(drawable, getContext().getTheme()).mutate();
+ icon.setBounds(0, 0, V.dp(18), V.dp(18));
+ icon.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface));
+ setCompoundDrawablesRelativeWithIntrinsicBounds(getCompoundDrawablesRelative()[0], null, icon, null);
+ updatePadding();
+ }
+
+ public void setDrawableStartTinted(@DrawableRes int drawable){
+ Drawable icon=getResources().getDrawable(drawable, getContext().getTheme()).mutate();
+ icon.setBounds(0, 0, V.dp(18), V.dp(18));
+ icon.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary));
+ setCompoundDrawablesRelative(icon, null, getCompoundDrawablesRelative()[2], null);
updatePadding();
}
}
diff --git a/mastodon/src/main/res/color/m3_primary_alpha11.xml b/mastodon/src/main/res/color/m3_primary_alpha11.xml
new file mode 100644
index 000000000..44c7b0b68
--- /dev/null
+++ b/mastodon/src/main/res/color/m3_primary_alpha11.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/drawable/bg_m3_surface3.xml b/mastodon/src/main/res/drawable/bg_m3_surface3.xml
new file mode 100644
index 000000000..c1b10d943
--- /dev/null
+++ b/mastodon/src/main/res/drawable/bg_m3_surface3.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/mastodon/src/main/res/drawable/ic_mood_20px.xml b/mastodon/src/main/res/drawable/ic_mood_20px.xml
new file mode 100644
index 000000000..a99bd218f
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_mood_20px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_search_20px.xml b/mastodon/src/main/res/drawable/ic_search_20px.xml
new file mode 100644
index 000000000..6758775ad
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_search_20px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml
index 4cd2a9b96..9b16d2866 100644
--- a/mastodon/src/main/res/values/strings.xml
+++ b/mastodon/src/main/res/values/strings.xml
@@ -481,4 +481,7 @@
Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.\n\nYou can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.\n\n- Capture important elements
\n- Summarize text in images
\n- Use regular sentence structure
\n- Avoid redundant information
\n- Focus on trends and key findings in complex visuals (like diagrams or maps)
Edit post
No verified link
+ Browse emoji
+ Find who you\'re looking for
+ Could not find anything for these search terms
\ No newline at end of file