[android] Retain SearchFragment search results across configuration changes

Signed-off-by: savsch <119003089+savsch@users.noreply.github.com>
This commit is contained in:
Tanmay Gupta 2025-03-11 22:29:54 +05:30
parent 5f503c5c25
commit c1a02e88ba
5 changed files with 181 additions and 13 deletions

View file

@ -2,6 +2,8 @@ package app.organicmaps.sdk.search;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -20,7 +22,7 @@ import app.organicmaps.util.Distance;
// Used by JNI.
@Keep
@SuppressWarnings("unused")
public class SearchResult
public class SearchResult implements Parcelable
{
public static final int TYPE_PURE_SUGGEST = 0;
public static final int TYPE_SUGGEST = 1;
@ -37,7 +39,7 @@ public class SearchResult
// Used by JNI.
@Keep
@SuppressWarnings("unused")
public static class Description
public static class Description implements Parcelable
{
public final FeatureId featureId;
public final String localizedFeatureType;
@ -65,6 +67,54 @@ public class SearchResult
this.minutesUntilClosed = minutesUntilClosed;
this.hasPopularityHigherPriority = hasPopularityHigherPriority;
}
public static final Creator<Description> CREATOR = new Creator<Description>()
{
@Override
public Description createFromParcel(Parcel in)
{
return new Description(in);
}
@Override
public Description[] newArray(int size)
{
return new Description[size];
}
};
protected Description(Parcel in)
{
featureId = in.readParcelable(FeatureId.class.getClassLoader());
localizedFeatureType = in.readString();
region = in.readString();
distance = in.readParcelable(Distance.class.getClassLoader());
description = in.readString();
openNow = in.readInt();
minutesUntilOpen = in.readInt();
minutesUntilClosed = in.readInt();
hasPopularityHigherPriority = in.readByte() != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeParcelable(featureId, flags);
dest.writeString(localizedFeatureType);
dest.writeString(region);
dest.writeParcelable(distance, flags);
dest.writeString(description);
dest.writeInt(openNow);
dest.writeInt(minutesUntilOpen);
dest.writeInt(minutesUntilClosed);
dest.writeByte((byte) (hasPopularityHigherPriority ? 1 : 0));
}
@Override
public int describeContents()
{
return 0;
}
}
public final String name;
@ -113,6 +163,19 @@ public class SearchResult
this.descHighlightRanges = descHighlightRanges;
}
protected SearchResult(Parcel in)
{
name = in.readString();
suggestion = in.readString();
lat = in.readDouble();
lon = in.readDouble();
type = in.readInt();
description = in.readParcelable(Description.class.getClassLoader());
highlightRanges = in.createIntArray();
descHighlightRanges = in.createIntArray();
mPopularity = in.readParcelable(Popularity.class.getClassLoader());
}
@NonNull
public String getTitle(@NonNull Context context)
{
@ -157,4 +220,38 @@ public class SearchResult
return builder;
}
}
public static final Creator<SearchResult> CREATOR = new Creator<>()
{
@Override
public SearchResult createFromParcel(Parcel in)
{
return new SearchResult(in);
}
@Override
public SearchResult[] newArray(int size)
{
return new SearchResult[size];
}
};
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(name);
dest.writeString(suggestion);
dest.writeDouble(lat);
dest.writeDouble(lon);
dest.writeInt(type);
dest.writeParcelable(description, flags);
dest.writeIntArray(highlightRanges);
dest.writeIntArray(descHighlightRanges);
dest.writeParcelable(mPopularity, flags);
}
}

View file

@ -256,6 +256,11 @@ class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHol
refreshData(null);
}
public SearchResult[] getResults()
{
return mResults;
}
void refreshData(@Nullable SearchResult[] results)
{
mResults = results;

View file

@ -65,8 +65,11 @@ public class SearchFragment extends BaseMwmFragment
public static final String ARG_LOCALE = "locale";
public static final String ARG_SEARCH_ON_MAP = "search_on_map";
private static final String STATE_KEY_RESULTS = "state_results";
private ModalSearchViewModel mViewModel;
private long mLastQueryTimestamp;
private boolean wereResultsRestored = false;
@NonNull
private final List<HiddenCommand> mHiddenCommands = new ArrayList<>();
@ -123,7 +126,11 @@ public class SearchFragment extends BaseMwmFragment
}
mViewModel.setIsQueryEmpty(false);
runSearch();
if (wereResultsRestored)
wereResultsRestored = false;
else
runSearch();
}
@Override
@ -254,7 +261,7 @@ public class SearchFragment extends BaseMwmFragment
| AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL : 0);
toolbar.setLayoutParams(lp);
UiUtils.showIf(hasQuery, mResultsFrame);
UiUtils.showIf(wereResultsRestored || hasQuery, mResultsFrame);
UiUtils.showIf(!mIsModal && hasQuery, mShowOnMapFab);
if (hasQuery)
hideDownloadSuggest();
@ -268,7 +275,7 @@ public class SearchFragment extends BaseMwmFragment
{
final boolean show = !mSearchRunning
&& mSearchAdapter.getItemCount() == 0
&& mToolbarController.hasQuery();
&& (wereResultsRestored || mToolbarController.hasQuery());
UiUtils.showIf(show, mResultsPlaceholder);
}
@ -325,6 +332,16 @@ public class SearchFragment extends BaseMwmFragment
mResults.setLayoutManager(new LinearLayoutManager(view.getContext()));
mResults.setAdapter(mSearchAdapter);
if (savedInstanceState != null)
{
SearchResult[] savedSearchResults = (SearchResult[]) savedInstanceState.getParcelableArray(STATE_KEY_RESULTS);
if (savedSearchResults != null)
{
wereResultsRestored = true;
mSearchAdapter.refreshData(savedSearchResults);
}
}
updateFrames();
updateResultsPlaceholder();
ViewCompat.setOnApplyWindowInsetsListener(
@ -382,6 +399,14 @@ public class SearchFragment extends BaseMwmFragment
mToolbarController.detach();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState)
{
super.onSaveInstanceState(outState);
if (!mSearchRunning)
outState.putParcelableArray(STATE_KEY_RESULTS, mSearchAdapter.getResults());
}
@Override
public void onDestroy()
{

View file

@ -1,6 +1,8 @@
package app.organicmaps.util;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@ -11,7 +13,7 @@ import app.organicmaps.R;
// Used by JNI.
@Keep
@SuppressWarnings("unused")
public final class Distance
public final class Distance implements Parcelable
{
public static final Distance EMPTY = new Distance(0.0, "", (byte) 0);
@ -50,6 +52,13 @@ public final class Distance
mUnits = Units.values()[unitsIndex];
}
protected Distance(Parcel in)
{
mDistance = in.readDouble();
mDistanceStr = in.readString();
mUnits = Units.values()[in.readByte()];
}
public boolean isValid()
{
return mDistance >= 0.0;
@ -70,6 +79,21 @@ public final class Distance
return mDistanceStr + NON_BREAKING_SPACE + getUnitsStr(context);
}
public static final Creator<Distance> CREATOR = new Creator<Distance>()
{
@Override
public Distance createFromParcel(Parcel in)
{
return new Distance(in);
}
@Override
public Distance[] newArray(int size)
{
return new Distance[size];
}
};
@NonNull
@Override
public String toString()
@ -79,4 +103,18 @@ public final class Distance
return mDistanceStr + NON_BREAKING_SPACE + mUnits.toString();
}
}
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeDouble(mDistance);
dest.writeString(mDistanceStr);
dest.writeByte((byte) mUnits.ordinal());
}
}

View file

@ -33,10 +33,12 @@ import app.organicmaps.util.InputUtils;
public class ModalSearchController extends Fragment
{
private static final String SEARCH_FRAGMENT_TAG = SearchFragment.class.getSimpleName();
private static final int FALLBACK_COLLAPSED_HEIGHT = 200;
private SearchBottomSheetBehavior<NestedScrollView> mSearchBehavior;
private NestedScrollView mModalSearch;
private ViewGroup mCoordinator;
private int mViewportMinHeight;
private int mCollapsedHeight = FALLBACK_COLLAPSED_HEIGHT;
private ModalSearchViewModel mViewModel;
private final Observer<Boolean> mModalSearchSuspendedObserver = suspended -> {
if (Boolean.FALSE.equals(mViewModel.getModalSearchActive().getValue()))
@ -235,12 +237,13 @@ public class ModalSearchController extends Fragment
{
try
{
return mDragIndicator.getMeasuredHeight() +
int calculatedHeight = mDragIndicator.getMeasuredHeight() +
mModalSearch.findViewById(R.id.app_bar).getMeasuredHeight(); // TODO(savsch) get feedback on whether to change this height
} catch (NullPointerException npe)
{
return 0;
}
if (calculatedHeight > 0)
mCollapsedHeight = calculatedHeight;
} catch (NullPointerException ignored)
{}
return mCollapsedHeight;
}
private void removeModalSearchFragments()