parent
badc75860b
commit
d08484689d
@ -0,0 +1,67 @@
|
||||
package net.micode.notes.tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchHistoryManager {
|
||||
private static final String PREF_NAME = "search_history";
|
||||
private static final String KEY_HISTORY = "history_list";
|
||||
private static final int MAX_HISTORY_SIZE = 10;
|
||||
|
||||
private final SharedPreferences mPrefs;
|
||||
|
||||
public SearchHistoryManager(Context context) {
|
||||
mPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public List<String> getHistory() {
|
||||
String json = mPrefs.getString(KEY_HISTORY, "");
|
||||
List<String> list = new ArrayList<>();
|
||||
if (TextUtils.isEmpty(json)) {
|
||||
return list;
|
||||
}
|
||||
try {
|
||||
JSONArray array = new JSONArray(json);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
list.add(array.getString(i));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void addHistory(String keyword) {
|
||||
if (TextUtils.isEmpty(keyword)) return;
|
||||
List<String> history = getHistory();
|
||||
// Remove existing to move to top
|
||||
history.remove(keyword);
|
||||
history.add(0, keyword);
|
||||
// Limit size
|
||||
if (history.size() > MAX_HISTORY_SIZE) {
|
||||
history = history.subList(0, MAX_HISTORY_SIZE);
|
||||
}
|
||||
saveHistory(history);
|
||||
}
|
||||
|
||||
public void removeHistory(String keyword) {
|
||||
List<String> history = getHistory();
|
||||
if (history.remove(keyword)) {
|
||||
saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
mPrefs.edit().remove(KEY_HISTORY).apply();
|
||||
}
|
||||
|
||||
private void saveHistory(List<String> history) {
|
||||
JSONArray array = new JSONArray(history);
|
||||
mPrefs.edit().putString(KEY_HISTORY, array.toString()).apply();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,188 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.NotesRepository;
|
||||
import net.micode.notes.tool.SearchHistoryManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NoteSearchActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, NoteSearchAdapter.OnItemClickListener {
|
||||
|
||||
private SearchView mSearchView;
|
||||
private RecyclerView mRecyclerView;
|
||||
private TextView mTvNoResult;
|
||||
private NoteSearchAdapter mAdapter;
|
||||
private NotesRepository mRepository;
|
||||
private SearchHistoryManager mHistoryManager;
|
||||
|
||||
private TextView mBtnShowHistory;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_note_search);
|
||||
|
||||
mRepository = new NotesRepository(getContentResolver());
|
||||
mHistoryManager = new SearchHistoryManager(this);
|
||||
|
||||
initViews();
|
||||
// Initial state: search is empty, show history button if there is history, or just show list
|
||||
// Requirement: "history option below search bar"
|
||||
showHistoryOption();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
mSearchView = findViewById(R.id.search_view);
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
mSearchView.setFocusable(true);
|
||||
mSearchView.setIconified(false);
|
||||
mSearchView.requestFocusFromTouch();
|
||||
|
||||
mBtnShowHistory = findViewById(R.id.btn_show_history);
|
||||
mBtnShowHistory.setOnClickListener(v -> showHistoryList());
|
||||
|
||||
mRecyclerView = findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
mAdapter = new NoteSearchAdapter(this, this);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
mTvNoResult = findViewById(R.id.tv_no_result);
|
||||
}
|
||||
|
||||
private void showHistoryOption() {
|
||||
// Show the "History" button, hide the list
|
||||
mBtnShowHistory.setVisibility(View.VISIBLE);
|
||||
mRecyclerView.setVisibility(View.GONE);
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showHistoryList() {
|
||||
List<String> history = mHistoryManager.getHistory();
|
||||
if (history.isEmpty()) {
|
||||
// If no history, maybe show a toast or empty state?
|
||||
// But for now, let's just show the empty list which is fine
|
||||
}
|
||||
List<Object> data = new ArrayList<>(history);
|
||||
mAdapter.setData(data, null);
|
||||
|
||||
mBtnShowHistory.setVisibility(View.GONE); // Hide button when showing list
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
mRecyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void performSearch(String query) {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
showHistoryOption();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide history button when searching
|
||||
mBtnShowHistory.setVisibility(View.GONE);
|
||||
|
||||
mRepository.searchNotes(query, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
|
||||
@Override
|
||||
public void onSuccess(List<NotesRepository.NoteInfo> result) {
|
||||
runOnUiThread(() -> {
|
||||
List<Object> data = new ArrayList<>(result);
|
||||
mAdapter.setData(data, query);
|
||||
if (data.isEmpty()) {
|
||||
mTvNoResult.setVisibility(View.VISIBLE);
|
||||
mRecyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
mRecyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(NoteSearchActivity.this, "Search failed: " + error.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
mHistoryManager.addHistory(query);
|
||||
performSearch(query);
|
||||
mSearchView.clearFocus(); // Hide keyboard
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if (TextUtils.isEmpty(newText)) {
|
||||
showHistoryOption();
|
||||
} else {
|
||||
performSearch(newText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoteClick(NotesRepository.NoteInfo note) {
|
||||
// Save history when user clicks a result
|
||||
String query = mSearchView.getQuery().toString();
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
mHistoryManager.addHistory(query);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, NoteEditActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.putExtra(Intent.EXTRA_UID, note.getId());
|
||||
// Pass search keyword for highlighting in editor
|
||||
// NoteEditActivity uses SearchManager.EXTRA_DATA_KEY for ID and USER_QUERY for keyword
|
||||
intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
|
||||
intent.putExtra(android.app.SearchManager.USER_QUERY, mSearchView.getQuery().toString());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryClick(String keyword) {
|
||||
mSearchView.setQuery(keyword, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryDelete(String keyword) {
|
||||
mHistoryManager.removeHistory(keyword);
|
||||
// Refresh history view if we are currently showing history (search box is empty)
|
||||
if (TextUtils.isEmpty(mSearchView.getQuery())) {
|
||||
showHistoryList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mRepository != null) {
|
||||
mRepository.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.Material3.ActionBar">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/ThemeOverlay.Material3.Light">
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionSearch|flagNoExtractUi"
|
||||
app:iconifiedByDefault="false"
|
||||
app:queryHint="@string/search_hint" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<!-- 历史记录按钮 -->
|
||||
<TextView
|
||||
android:id="@+id/btn_show_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:text="@string/search_history_title"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:drawablePadding="8dp"
|
||||
android:visibility="gone"
|
||||
app:drawableEndCompat="@android:drawable/arrow_down_float" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_no_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/search_no_results"
|
||||
android:visibility="gone"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_menu_recent_history"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_history_keyword"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_delete_history"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:padding="4dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:contentDescription="@string/menu_delete" />
|
||||
|
||||
</LinearLayout>
|
||||
Loading…
Reference in new issue