Add search back!

This commit is contained in:
Ammar Githam 2020-09-06 02:02:54 +09:00
parent 48b76c231a
commit 89bb79a8fc
11 changed files with 343 additions and 78 deletions

View File

@ -1,15 +1,24 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.database.MatrixCursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.BaseColumns;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.lifecycle.LiveData;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.ui.NavigationUI;
@ -19,11 +28,17 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.helpers.CustomHideBottomViewOnScrollBehavior;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
@ -55,6 +70,12 @@ public class MainActivity extends BaseLanguageActivity {
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData;
private MenuItem searchMenuItem;
private SuggestionsAdapter suggestionAdapter;
private AutoCompleteTextView searchAutoComplete;
private SearchView searchView;
private boolean showSearch = true;
private Handler suggestionsFetchHandler;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
@ -63,15 +84,39 @@ public class MainActivity extends BaseLanguageActivity {
final String cookie = settingsHelper.getString(Constants.COOKIE);
Utils.setupCookies(cookie);
setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
if (savedInstanceState == null) {
setupBottomNavigationBar();
}
setupScrollingListener();
setupSuggestions();
}
private void setupSuggestions() {
suggestionsFetchHandler = new Handler();
suggestionAdapter = new SuggestionsAdapter(this, (type, query) -> {
if (searchMenuItem != null) searchMenuItem.collapseActionView();
if (searchView != null && !searchView.isIconified()) searchView.setIconified(true);
if (currentNavControllerLiveData != null && currentNavControllerLiveData.getValue() != null) {
final NavController navController = currentNavControllerLiveData.getValue();
final Bundle bundle = new Bundle();
switch (type) {
case TYPE_LOCATION:
bundle.putString("locationId", query);
navController.navigate(R.id.action_global_locationFragment, bundle);
break;
case TYPE_HASHTAG:
bundle.putString("hashtag", query);
navController.navigate(R.id.action_global_hashTagFragment, bundle);
break;
case TYPE_USER:
bundle.putString("username", query);
navController.navigate(R.id.action_global_profileFragment, bundle);
break;
}
}
});
}
private void setupScrollingListener() {
@ -83,11 +128,132 @@ public class MainActivity extends BaseLanguageActivity {
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
searchMenuItem = menu.findItem(R.id.search);
if (!showSearch) {
searchMenuItem.setVisible(false);
return true;
}
return setupSearchView();
}
private boolean setupSearchView() {
final View actionView = searchMenuItem.getActionView();
if (!(actionView instanceof SearchView)) return false;
searchView = (SearchView) actionView;
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setMaxWidth(Integer.MAX_VALUE);
final View searchText = searchView.findViewById(R.id.search_src_text);
if (searchText instanceof AutoCompleteTextView) {
searchAutoComplete = (AutoCompleteTextView) searchText;
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
}
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
private boolean searchUser;
private boolean searchHash;
private AsyncTask<?, ?, ?> prevSuggestionAsync;
private final String[] COLUMNS = {
BaseColumns._ID,
Constants.EXTRAS_USERNAME,
Constants.EXTRAS_NAME,
Constants.EXTRAS_TYPE,
"pfp",
"verified"
};
private String currentSearchQuery;
private final FetchListener<SuggestionModel[]> fetchListener = new FetchListener<SuggestionModel[]>() {
@Override
public void doBefore() {
suggestionAdapter.changeCursor(null);
}
@Override
public void onResult(final SuggestionModel[] result) {
final MatrixCursor cursor;
if (result == null) cursor = null;
else {
cursor = new MatrixCursor(COLUMNS, 0);
for (int i = 0; i < result.length; i++) {
final SuggestionModel suggestionModel = result[i];
if (suggestionModel != null) {
final SuggestionType suggestionType = suggestionModel.getSuggestionType();
final Object[] objects = {
i,
suggestionType == SuggestionType.TYPE_LOCATION ? suggestionModel.getName() : suggestionModel.getUsername(),
suggestionType == SuggestionType.TYPE_LOCATION ? suggestionModel.getUsername() : suggestionModel.getName(),
suggestionType,
suggestionModel.getProfilePic(),
suggestionModel.isVerified()};
if (!searchHash && !searchUser) cursor.addRow(objects);
else {
final boolean isCurrHash = suggestionType == SuggestionType.TYPE_HASHTAG;
if (searchHash && isCurrHash || !searchHash && !isCurrHash)
cursor.addRow(objects);
}
}
}
}
suggestionAdapter.changeCursor(cursor);
}
};
private final Runnable runnable = () -> {
cancelSuggestionsAsync();
if (Utils.isEmpty(currentSearchQuery)) {
suggestionAdapter.changeCursor(null);
return;
}
searchUser = currentSearchQuery.charAt(0) == '@';
searchHash = currentSearchQuery.charAt(0) == '#';
if (currentSearchQuery.length() == 1 && (searchHash || searchUser)) {
if (searchAutoComplete != null) {
searchAutoComplete.setThreshold(2);
}
} else {
if (searchAutoComplete != null) {
searchAutoComplete.setThreshold(1);
}
prevSuggestionAsync = new SuggestionsFetcher(fetchListener).executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR,
searchUser || searchHash ? currentSearchQuery.substring(1)
: currentSearchQuery);
}
};
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {}
}
@Override
public boolean onQueryTextSubmit(final String query) {
return onQueryTextChange(query);
// menu.findItem(R.id.action_about).setVisible(true);
// menu.findItem(R.id.action_settings).setVisible(true);
// closeAnyOpenDrawer();
// addToStack();
// userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
// searchAction.collapseActionView();
// searchView.setIconified(true);
// searchView.setIconified(true);
// mainHelper.onRefresh();
}
@Override
public boolean onQueryTextChange(final String query) {
suggestionsFetchHandler.removeCallbacks(runnable);
currentSearchQuery = query;
suggestionsFetchHandler.postDelayed(runnable, 800);
return true;
}
});
return true;
}
private void setupBottomNavigationBar() {
final List<Integer> navList = new ArrayList<>(Arrays.asList(
final List<Integer> mainNavList = new ArrayList<>(Arrays.asList(
R.navigation.direct_messages_nav_graph,
R.navigation.feed_nav_graph,
R.navigation.profile_nav_graph,
@ -96,7 +262,7 @@ public class MainActivity extends BaseLanguageActivity {
));
final LiveData<NavController> navControllerLiveData = setupWithNavController(
binding.bottomNavView,
navList,
mainNavList,
getSupportFragmentManager(),
R.id.main_nav_host,
getIntent(),
@ -108,8 +274,11 @@ public class MainActivity extends BaseLanguageActivity {
private void setupNavigation(final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
// below is a hack to check if we are at the end of the current stack, to setup the search view
binding.appBarLayout.setExpanded(true, true);
final int destinationId = destination.getId();
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
binding.bottomNavView.setVisibility(SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId) ? View.VISIBLE : View.GONE);
if (KEEP_SCROLL_BEHAVIOUR_DESTINATIONS.contains(destinationId)) {
setScrollingBehaviour();
@ -124,6 +293,17 @@ public class MainActivity extends BaseLanguageActivity {
});
}
private void setupMenu(final int backStackSize, final int destinationId) {
if (searchMenuItem == null) return;
if (backStackSize == 2 && destinationId != R.id.morePreferencesFragment) {
showSearch = true;
searchMenuItem.setVisible(true);
return;
}
showSearch = false;
searchMenuItem.setVisible(false);
}
private void setScrollingBehaviour() {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams();
layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());

View File

@ -2,60 +2,87 @@ package awais.instagrabber.adapters;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.CursorAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.models.enums.SuggestionType;
public final class SuggestionsAdapter extends CursorAdapter {
private final LayoutInflater layoutInflater;
private final View.OnClickListener onClickListener;
private final RequestManager glideRequestManager;
private static final String TAG = "SuggestionsAdapter";
public SuggestionsAdapter(final Context context, final View.OnClickListener onClickListener) {
private final OnSuggestionClickListener onSuggestionClickListener;
public SuggestionsAdapter(final Context context,
final OnSuggestionClickListener onSuggestionClickListener) {
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER);
this.glideRequestManager = Glide.with(context);
this.layoutInflater = LayoutInflater.from(context);
this.onClickListener = onClickListener;
this.onSuggestionClickListener = onSuggestionClickListener;
}
@Override
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
return layoutInflater.inflate(R.layout.item_suggestion, parent, false);
final LayoutInflater layoutInflater = LayoutInflater.from(context);
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(layoutInflater, parent, false);
return binding.getRoot();
// return layoutInflater.inflate(R.layout.item_suggestion, parent, false);
}
@Override
public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) {
// i, username, fullname, type, picUrl, verified
// 0, 1 , 2 , 3 , 4 , 5
final String fullname = cursor.getString(2);
final String fullName = cursor.getString(2);
String username = cursor.getString(1);
final String picUrl = cursor.getString(4);
final boolean verified = cursor.getString(5).charAt(0) == 't';
if ("TYPE_HASHTAG".equals(cursor.getString(3))) username = '#' + username;
else if ("TYPE_USER".equals(cursor.getString(3))) username = '@' + username;
final String type = cursor.getString(3);
SuggestionType suggestionType = null;
try {
suggestionType = SuggestionType.valueOf(type);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unknown suggestion type: " + type, e);
}
if (suggestionType == null) return;
final String query;
switch (suggestionType) {
case TYPE_USER:
username = '@' + username;
query = username;
break;
case TYPE_HASHTAG:
username = '#' + username;
query = username;
break;
case TYPE_LOCATION:
query = fullName;
break;
default:
return; // will never come here
}
view.setOnClickListener(onClickListener);
view.setTag("TYPE_LOCATION".equals(cursor.getString(3)) ? fullname : username);
if (onSuggestionClickListener != null) {
final SuggestionType finalSuggestionType = suggestionType;
view.setOnClickListener(v -> onSuggestionClickListener.onSuggestionClick(finalSuggestionType, query));
}
final ItemSuggestionBinding binding = ItemSuggestionBinding.bind(view);
binding.isVerified.setVisibility(verified ? View.VISIBLE : View.GONE);
binding.tvUsername.setText(username);
if (suggestionType.equals(SuggestionType.TYPE_LOCATION)) {
binding.tvFullName.setVisibility(View.GONE);
} else {
binding.tvFullName.setVisibility(View.VISIBLE);
binding.tvFullName.setText(fullName);
}
binding.ivProfilePic.setImageURI(picUrl);
}
view.findViewById(R.id.isVerified).setVisibility(verified ? View.VISIBLE : View.GONE);
((TextView) view.findViewById(R.id.tvUsername)).setText(username);
((TextView) view.findViewById(R.id.tvFullName)).setText(fullname);
glideRequestManager.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true))
.load(picUrl == null ? R.drawable.ic_location : picUrl).into((ImageView) view.findViewById(R.id.ivProfilePic));
public interface OnSuggestionClickListener {
void onSuggestionClick(final SuggestionType type, final String query);
}
}

View File

@ -75,9 +75,9 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
// name
suggestionModels.add(new SuggestionModel(false,
place.getJSONObject("location").getString("pk")+"/"+place.getString("slug"),
place.getJSONObject("location").getString("pk"), // +"/"+place.getString("slug"),
place.getString("title"),
place.optString("profile_pic_url", null),
place.optString("profile_pic_url"),
SuggestionType.TYPE_LOCATION,
placesArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
}

View File

@ -97,7 +97,7 @@ public class NavigationExtensions {
}
return false;
});
setupItemReselected(bottomNavigationView, graphIdToTagMap, fragmentManager);
// setupItemReselected(bottomNavigationView, graphIdToTagMap, fragmentManager);
setupDeepLinks(bottomNavigationView, navGraphIds, fragmentManager, containerId, intent);
final int finalFirstFragmentGraphId = firstFragmentGraphId;
fragmentManager.addOnBackStackChangedListener(() -> {

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@ -25,12 +25,13 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.PrimarySurface"
style="@style/Widget.AppTheme.Toolbar.PrimarySurface"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="none"
app:popupTheme="@style/Widget.MaterialComponents.Toolbar.PrimarySurface"
app:title="@string/app_name" />
app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface"
app:title="@string/app_name"
tools:menu="@menu/main_menu" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -1,52 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:foreground="?android:selectableItemBackground"
tools:viewBindingIgnore="true"
android:padding="2dp">
android:padding="8dp">
<androidx.appcompat.widget.AppCompatImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvUsername"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true"
tools:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="44dp"
android:layout_marginLeft="44dp"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvUsername"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center_vertical"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/tvFullName"
app:layout_constraintEnd_toStartOf="@id/isVerified"
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
app:layout_constraintTop_toTopOf="parent"
tools:text="username" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvUsername"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat"
app:autoSizeMaxTextSize="24sp"
app:autoSizeTextType="uniform" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvFullName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
app:autoSizeMaxTextSize="18sp"
app:autoSizeTextType="uniform" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvFullName"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center_vertical"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
tools:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
app:layout_constraintTop_toBottomOf="@id/tvUsername"
tools:text="full name" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="end"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/verified" />
</FrameLayout>
app:layout_constraintBaseline_toBaselineOf="@id/tvUsername"
app:layout_constraintBottom_toTopOf="@id/tvFullName"
app:layout_constraintStart_toEndOf="@id/tvUsername"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/verified"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--<item-->
<!-- android:id="@+id/favourites"-->
<!-- android:enabled="true"-->
@ -14,4 +15,11 @@
<!-- android:title="@string/action_dms"-->
<!-- app:showAsAction="always" />-->
<item
android:id="@+id/search"
android:enabled="true"
android:icon="@drawable/ic_search_24"
android:title="@string/action_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView" />
</menu>

View File

@ -16,6 +16,28 @@
app:nullable="false" />
</action>
<include app:graph="@navigation/location_nav_graph" />
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/hashtag_nav_graph" />
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"

View File

@ -36,6 +36,10 @@
<style name="ThemeOverlay.App.BottomNavigationView" parent="" />
<style name="Widget.AppTheme.Toolbar.PrimarySurface" parent="Widget.MaterialComponents.Toolbar.PrimarySurface" />
<style name="ThemeOverlay.AppTheme.Dark.ActionBar" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="titleTextStyle">?attr/textAppearanceHeadline6</item>
<item name="subtitleTextStyle">?attr/textAppearanceSubtitle1</item>

View File

@ -8,13 +8,12 @@
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Widget.App.ActionMode</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.Dark.ActionBar</item>
<item name="actionBarTheme">@style/ThemeOverlay.AppTheme.Dark.ActionBar</item>
</style>
<style name="Theme.Amoled" parent="Theme.AppCompat.NoActionBar">
<style name="Theme.Amoled" parent="AppTheme">
<item name="android:colorBackground">@color/background_amoled</item>
<item name="bottomSheetDialogTheme">@style/AppTheme.BottomSheetDialog.Dark</item>
<item name="android:windowAnimationStyle">@style/AppTheme.WindowAnimationTransition</item>
</style>
<style name="AppTheme.BottomSheetDialog.Dark" parent="Theme.MaterialComponents.BottomSheetDialog" />