@ -20,6 +20,7 @@ import android.app.Activity;
import net.micode.notes.ui.PasswordInputActivity ;
import android.app.AlertDialog ;
import android.app.Dialog ;
import android.app.SearchManager ;
import android.appwidget.AppWidgetManager ;
import android.content.AsyncQueryHandler ;
import android.content.ContentResolver ;
@ -56,6 +57,8 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener ;
import android.widget.Button ;
import android.widget.EditText ;
import android.widget.ImageButton ;
import android.widget.LinearLayout ;
import android.widget.ListView ;
import android.widget.PopupMenu ;
import android.widget.TextView ;
@ -65,6 +68,7 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes ;
import net.micode.notes.data.Notes.NoteColumns ;
import net.micode.notes.data.Notes.TextNote ;
import net.micode.notes.data.NotesDatabaseHelper ;
import net.micode.notes.gtask.remote.GTaskSyncService ;
import net.micode.notes.model.ChecklistManager ;
import net.micode.notes.model.WorkingNote ;
@ -80,9 +84,14 @@ import java.io.IOException;
import java.io.InputStream ;
import java.io.InputStreamReader ;
import java.util.HashSet ;
import java.util.ArrayList ;
import java.util.LinkedHashSet ;
import java.util.Set ;
import android.os.Build ;
import android.view.Gravity ;
import android.view.ViewConfiguration ;
import android.graphics.Color ;
import java.lang.reflect.Field ;
/ * *
@ -99,6 +108,26 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private static final int MENU_FOLDER_VIEW = 1 ;
// 标签云相关控件
private LinearLayout mTagCloudSection ; // 标签云区域
private LinearLayout mTagCloudContainer ; // 标签云容器
private Button mAllNotesTag ; // 全部笔记标签
private String mCurrentFilterTag ; // 当前过滤标签
// 搜索历史相关
private static final String PREFERENCE_SEARCH_HISTORY = "search_history" ; // 搜索历史偏好设置键
private static final int MAX_SEARCH_HISTORY = 20 ; // 最大搜索历史记录数
private String mSearchQuery ; // 当前搜索查询字符串
private boolean mIsSearchMode ; // 是否处于搜索模式
// 实时搜索相关
private LinearLayout mSearchBar ; // 搜索栏容器
private EditText mSearchEditText ; // 搜索输入框
private ImageButton mClearSearchButton ; // 清除搜索按钮
private static final int SEARCH_DEBOUNCE_DELAY = 300 ; // 搜索防抖延迟时间(毫秒)
private Runnable mSearchRunnable ; // 搜索任务
private long mLastSearchTime ; // 上次搜索时间
private static final int MENU_FOLDER_CHANGE_NAME = 2 ;
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction" ;
@ -277,6 +306,73 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mIsChecklistMode = false ;
updateModeButtons ( ) ;
mChecklistManager = new ChecklistManager ( mContentResolver ) ;
// 初始化搜索相关控件
mSearchBar = ( LinearLayout ) findViewById ( R . id . search_bar ) ;
mSearchEditText = findViewById ( R . id . et_search ) ;
mClearSearchButton = ( ImageButton ) findViewById ( R . id . btn_clear_search ) ;
// 设置搜索监听器
initSearchListeners ( ) ;
// 标签云功能已移除,相关代码已注释
// mTagCloudSection = findViewById(R.id.tag_cloud_section);
// mTagCloudContainer = findViewById(R.id.tag_cloud_container);
// mAllNotesTag = findViewById(R.id.tag_all_notes);
// mCurrentFilterTag = null;
//
// // 加载标签云
// loadTagCloud();
}
/ * *
* 初 始 化 搜 索 相 关 监 听 器
* /
private void initSearchListeners ( ) {
// 初始化搜索任务
mSearchRunnable = new Runnable ( ) {
@Override
public void run ( ) {
// 执行搜索查询
setSearchMode ( ! TextUtils . isEmpty ( mSearchQuery ) , mSearchQuery ) ;
}
} ;
// 搜索输入框文本变化监听器
mSearchEditText . addTextChangedListener ( new TextWatcher ( ) {
@Override
public void beforeTextChanged ( CharSequence s , int start , int count , int after ) {
// 文本变化前的处理
}
@Override
public void onTextChanged ( CharSequence s , int start , int before , int count ) {
// 文本变化时的处理
mSearchQuery = s . toString ( ) ;
// 移除之前的搜索任务
mSearchEditText . removeCallbacks ( mSearchRunnable ) ;
// 延迟执行搜索任务
mSearchEditText . postDelayed ( mSearchRunnable , SEARCH_DEBOUNCE_DELAY ) ;
}
@Override
public void afterTextChanged ( Editable s ) {
// 文本变化后的处理
}
} ) ;
// 清除搜索按钮点击监听器
mClearSearchButton . setOnClickListener ( new OnClickListener ( ) {
@Override
public void onClick ( View v ) {
// 清除搜索内容
mSearchEditText . setText ( "" ) ;
// 移除未执行的搜索任务
mSearchEditText . removeCallbacks ( mSearchRunnable ) ;
// 退出搜索模式
exitSearchMode ( ) ;
}
} ) ;
}
/ * *
@ -500,32 +596,185 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
String selection ;
String [ ] selectionArgs ;
if ( mCurrentFolderId = = Notes . ID_ROOT_FOLDER ) {
if ( mIsChecklistMode ) {
// 根文件夹下的清单模式:只显示清单类型的笔记
selection = "(" + NoteColumns . TYPE + "=" + Notes . TYPE_CHECKLIST + " AND " + NoteColumns . PARENT_ID + "=?)" +
// 基础查询条件
if ( mIsSearchMode ) {
// 搜索模式:忽略清单模式,搜索所有笔记和清单
if ( mCurrentFolderId = = Notes . ID_ROOT_FOLDER ) {
// 根文件夹下的搜索:显示所有类型的笔记(普通笔记、清单、文件夹、通话记录文件夹)
selection = "(" + NoteColumns . TYPE + " IN (" + Notes . TYPE_NOTE + ", " + Notes . TYPE_CHECKLIST + ", " + Notes . TYPE_FOLDER + ") AND " + NoteColumns . PARENT_ID + "=?)" +
" OR (" + NoteColumns . ID + "=" + Notes . ID_CALL_RECORD_FOLDER + " AND " + NoteColumns . NOTES_COUNT + ">0)" ;
} else {
// 根文件夹下的笔记模式:显示文件夹、普通笔记和非空通话记录文件夹
selection = "(" + NoteColumns . TYPE + "<>" + Notes . TYPE_SYSTEM + " AND " + NoteColumns . PARENT_ID + "=?)" +
" OR (" + NoteColumns . ID + "=" + Notes . ID_CALL_RECORD_FOLDER + " AND " + NoteColumns . NOTES_COUNT + ">0)" ;
// 普通文件夹下的搜索:显示所有类型的笔记(普通笔记和清单)
selection = NoteColumns . PARENT_ID + "=? AND " + NoteColumns . TYPE + " IN (" + Notes . TYPE_NOTE + ", " + Notes . TYPE_CHECKLIST + ")" ;
}
} else {
if ( mIsChecklistMode ) {
// 普通文件夹下的清单模式:只显示清单类型的笔记
selection = NoteColumns . PARENT_ID + "=? AND " + NoteColumns . TYPE + "=" + Notes . TYPE_CHECKLIST ;
// 非搜索模式:使用原来的逻辑
if ( mCurrentFolderId = = Notes . ID_ROOT_FOLDER ) {
if ( mIsChecklistMode ) {
// 根文件夹下的清单模式:只显示清单类型的笔记
selection = "(" + NoteColumns . TYPE + "=" + Notes . TYPE_CHECKLIST + " AND " + NoteColumns . PARENT_ID + "=?)" +
" OR (" + NoteColumns . ID + "=" + Notes . ID_CALL_RECORD_FOLDER + " AND " + NoteColumns . NOTES_COUNT + ">0)" ;
} else {
// 根文件夹下的笔记模式:显示文件夹、普通笔记和非空通话记录文件夹
selection = "(" + NoteColumns . TYPE + "<>" + Notes . TYPE_SYSTEM + " AND " + NoteColumns . PARENT_ID + "=?)" +
" OR (" + NoteColumns . ID + "=" + Notes . ID_CALL_RECORD_FOLDER + " AND " + NoteColumns . NOTES_COUNT + ">0)" ;
}
} else {
// 普通文件夹下的笔记模式:只显示普通笔记
selection = NoteColumns . PARENT_ID + "=? AND " + NoteColumns . TYPE + "=" + Notes . TYPE_NOTE ;
if ( mIsChecklistMode ) {
// 普通文件夹下的清单模式:只显示清单类型的笔记
selection = NoteColumns . PARENT_ID + "=? AND " + NoteColumns . TYPE + "=" + Notes . TYPE_CHECKLIST ;
} else {
// 普通文件夹下的笔记模式:只显示普通笔记
selection = NoteColumns . PARENT_ID + "=? AND " + NoteColumns . TYPE + "=" + Notes . TYPE_NOTE ;
}
}
}
// 添加搜索过滤条件
if ( mIsSearchMode & & ! TextUtils . isEmpty ( mSearchQuery ) ) {
// 搜索过滤需要使用子查询, 因为内容存储在data表中
String searchFilter = " AND " + NoteColumns . ID + " IN (" +
"SELECT " + Notes . DataColumns . NOTE_ID +
" FROM " + NotesDatabaseHelper . TABLE . DATA +
" WHERE (" + Notes . DataColumns . MIME_TYPE + "=?" +
" AND " + Notes . DataColumns . CONTENT + " LIKE ?" +
") OR (" + Notes . DataColumns . MIME_TYPE + "=?" +
" AND " + Notes . DataColumns . CONTENT + " LIKE ?" +
"))" ;
selection + = searchFilter ;
}
// 添加标签过滤条件
if ( mCurrentFilterTag ! = null ) {
// 标签过滤需要使用子查询, 因为标签存储在data表中
String tagFilter = " AND " + NoteColumns . ID + " IN (" +
"SELECT " + Notes . DataColumns . NOTE_ID +
" FROM " + NotesDatabaseHelper . TABLE . DATA +
" WHERE " + Notes . DataColumns . MIME_TYPE + "=?" +
" AND " + Notes . DataColumns . CONTENT + "=?" +
")" ;
selection + = tagFilter ;
}
// 准备选择参数
ArrayList < String > argsList = new ArrayList < > ( ) ;
argsList . add ( String . valueOf ( mCurrentFolderId ) ) ;
// 添加搜索参数
if ( mIsSearchMode & & ! TextUtils . isEmpty ( mSearchQuery ) ) {
String searchPattern = "%" + mSearchQuery + "%" ;
// 搜索普通文本内容
argsList . add ( Notes . DataConstants . NOTE ) ;
argsList . add ( searchPattern ) ;
// 搜索标签
argsList . add ( Notes . DataConstants . TAG ) ;
argsList . add ( searchPattern ) ;
}
// 添加标签参数
if ( mCurrentFilterTag ! = null ) {
argsList . add ( Notes . DataConstants . TAG ) ;
argsList . add ( mCurrentFilterTag ) ;
}
selectionArgs = argsList . toArray ( new String [ argsList . size ( ) ] ) ;
mBackgroundQueryHandler . startQuery ( FOLDER_NOTE_LIST_QUERY_TOKEN , null ,
Notes . CONTENT_NOTE_URI , NoteItemData . PROJECTION , selection , new String [ ] {
String . valueOf ( mCurrentFolderId )
} , NoteColumns . TYPE + " DESC," + NoteColumns . MODIFIED_DATE + " DESC" ) ;
Notes . CONTENT_NOTE_URI , NoteItemData . PROJECTION , selection , selectionArgs ,
NoteColumns . TYPE + " DESC," + NoteColumns . MODIFIED_DATE + " DESC" ) ;
}
/ * *
* 加 载 标 签 云
* /
private void loadTagCloud ( ) {
// 清空现有标签(保留"全部笔记"标签)
while ( mTagCloudContainer . getChildCount ( ) > 1 ) {
mTagCloudContainer . removeViewAt ( 1 ) ;
}
// 查询所有标签,按使用次数排序
String tagQuery = "SELECT " + Notes . DataColumns . CONTENT +
" FROM " + NotesDatabaseHelper . TABLE . DATA +
" WHERE " + Notes . DataColumns . MIME_TYPE + "=?" +
" GROUP BY " + Notes . DataColumns . CONTENT +
" ORDER BY COUNT(*) DESC" ;
Cursor cursor = mContentResolver . query (
Notes . CONTENT_DATA_URI ,
new String [ ] { Notes . DataColumns . CONTENT } ,
Notes . DataColumns . MIME_TYPE + "=?" ,
new String [ ] { Notes . DataConstants . TAG } ,
Notes . DataColumns . CONTENT + " ASC" ) ;
if ( cursor ! = null ) {
while ( cursor . moveToNext ( ) ) {
String tagName = cursor . getString ( 0 ) ;
if ( ! TextUtils . isEmpty ( tagName ) ) {
// 创建标签按钮
Button tagButton = new Button ( this ) ;
tagButton . setText ( tagName ) ;
tagButton . setBackgroundResource ( R . drawable . bg_btn_set_color ) ;
tagButton . setTextColor ( Color . WHITE ) ;
tagButton . setTextSize ( 14 ) ;
tagButton . setSingleLine ( true ) ;
tagButton . setTag ( tagName ) ;
// tagButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// onTagClick(v);
// }
// });
// 设置标准化布局参数
LinearLayout . LayoutParams params = new LinearLayout . LayoutParams (
LinearLayout . LayoutParams . WRAP_CONTENT , 36 ) ;
params . setMargins ( 0 , 0 , 8 , 0 ) ;
params . gravity = Gravity . CENTER_VERTICAL ;
tagButton . setLayoutParams ( params ) ;
// 设置标准化内边距
tagButton . setPadding ( 12 , 0 , 12 , 0 ) ;
// 添加到标签云容器
mTagCloudContainer . addView ( tagButton ) ;
}
}
cursor . close ( ) ;
}
}
// 标签云功能已移除, onTagClick方法已删除
// public void onTagClick(View v) {
// if (v.getId() == R.id.tag_all_notes) {
// // 显示全部笔记
// mCurrentFilterTag = null;
// mAllNotesTag.setTextColor(Color.WHITE);
// mAllNotesTag.setBackgroundResource(R.drawable.bg_btn_set_color);
// } else {
// // 显示特定标签的笔记
// mCurrentFilterTag = (String) v.getTag();
// mAllNotesTag.setTextColor(Color.BLACK);
// mAllNotesTag.setBackgroundResource(android.R.drawable.btn_default);
// }
//
// // 更新其他标签的样式
// for (int i = 1; i < mTagCloudContainer.getChildCount(); i++) {
// Button tagButton = (Button) mTagCloudContainer.getChildAt(i);
// if (tagButton.getTag().equals(mCurrentFilterTag)) {
// tagButton.setTextColor(Color.WHITE);
// tagButton.setBackgroundResource(R.drawable.bg_btn_set_color);
// } else {
// tagButton.setTextColor(Color.BLACK);
// tagButton.setBackgroundResource(android.R.drawable.btn_default);
// }
// }
//
// // 重新查询笔记列表
// startAsyncNotesListQuery();
// }
/ * *
* 后 台 查 询 处 理 器 - 处 理 异 步 数 据 库 查 询
* /
@ -615,7 +864,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
View view = LayoutInflater . from ( this ) . inflate ( R . layout . dialog_edit_text , null ) ;
final EditText etPassword = ( EditText ) view . findViewById ( R . id . et_foler_name ) ;
etPassword . setHint ( R . string . password_hint ) ;
etPassword . setInputType ( android . text . InputType . TYPE_CLASS_TEXT |
etPassword . setInputType ( android . text . InputType . TYPE_CLASS_TEXT |
android . text . InputType . TYPE_TEXT_VARIATION_PASSWORD ) ;
builder . setTitle ( R . string . password_set_dialog_title ) ;
builder . setView ( view ) ;
@ -630,14 +879,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
final Dialog dialog = builder . create ( ) ;
dialog . show ( ) ;
final Button positive = ( Button ) dialog . findViewById ( android . R . id . button1 ) ;
positive . setOnClickListener ( new OnClickListener ( ) {
@Override
public void onClick ( View v ) {
String password = etPassword . getText ( ) . toString ( ) ;
if ( TextUtils . isEmpty ( password ) ) {
Toast . makeText ( NotesListActivity . this ,
Toast . makeText ( NotesListActivity . this ,
R . string . password_empty , Toast . LENGTH_SHORT ) . show ( ) ;
return ;
}
@ -1175,12 +1424,67 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
startActivity ( intent ) ;
break ;
}
case R . id . menu_exit_search :
// 退出搜索模式
exitSearchMode ( ) ;
return true ;
default :
break ;
}
return true ;
}
@Override
protected void onNewIntent ( Intent intent ) {
super . onNewIntent ( intent ) ;
// 处理搜索意图
if ( Intent . ACTION_SEARCH . equals ( intent . getAction ( ) ) ) {
String query = intent . getStringExtra ( SearchManager . QUERY ) ;
if ( ! TextUtils . isEmpty ( query ) ) {
setSearchMode ( true , query ) ;
// 添加到搜索历史
saveSearchHistory ( query ) ;
// 显示软键盘
InputMethodManager imm = ( InputMethodManager ) getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
if ( imm ! = null ) {
imm . showSoftInput ( mSearchEditText , InputMethodManager . SHOW_IMPLICIT ) ;
}
}
}
}
/ * *
* 设 置 搜 索 模 式
* @param isSearchMode 是 否 处 于 搜 索 模 式
* @param searchQuery 搜 索 查 询 字 符 串
* /
private void setSearchMode ( boolean isSearchMode , String searchQuery ) {
mIsSearchMode = isSearchMode ;
mSearchQuery = searchQuery ;
startAsyncNotesListQuery ( ) ;
// 更新UI, 显示或隐藏搜索相关控件
if ( isSearchMode ) {
// 搜索模式下显示搜索栏
mSearchBar . setVisibility ( View . VISIBLE ) ;
// 设置搜索查询文本
mSearchEditText . setText ( searchQuery ) ;
// 将光标定位到文本末尾
mSearchEditText . setSelection ( searchQuery . length ( ) ) ;
} else {
// 退出搜索模式,隐藏搜索栏
mSearchBar . setVisibility ( View . GONE ) ;
}
}
/ * *
* 退 出 搜 索 模 式
* /
public void exitSearchMode ( ) {
setSearchMode ( false , null ) ;
}
@Override
public boolean onSearchRequested ( ) {
startSearch ( null , false , null /* appData */ , false ) ;
@ -1192,8 +1496,85 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* OMO
* /
public void handleSearchClick ( View view ) {
// 调用系统搜索方法
onSearchRequested ( ) ;
// 显示搜索栏并聚焦到搜索输入框
setSearchMode ( true , mSearchQuery ! = null ? mSearchQuery : "" ) ;
mSearchEditText . requestFocus ( ) ;
// 显示软键盘
InputMethodManager imm = ( InputMethodManager ) getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
if ( imm ! = null ) {
imm . showSoftInput ( mSearchEditText , InputMethodManager . SHOW_IMPLICIT ) ;
}
}
/ * *
* 保 存 搜 索 历 史
* @param query 搜 索 查 询 字 符 串
* /
private void saveSearchHistory ( String query ) {
if ( TextUtils . isEmpty ( query ) ) {
return ;
}
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( this ) ;
Set < String > historySet = prefs . getStringSet ( PREFERENCE_SEARCH_HISTORY , new HashSet < String > ( ) ) ;
// 创建新的集合以支持修改
Set < String > newHistorySet = new LinkedHashSet < String > ( historySet ) ;
// 如果已存在,先移除,然后添加到开头
newHistorySet . remove ( query ) ;
// 创建有序集合以保持插入顺序
LinkedHashSet < String > orderedHistorySet = new LinkedHashSet < String > ( ) ;
orderedHistorySet . add ( query ) ;
orderedHistorySet . addAll ( newHistorySet ) ;
// 限制历史记录数量
if ( orderedHistorySet . size ( ) > MAX_SEARCH_HISTORY ) {
// 移除最旧的记录
String [ ] historyArray = orderedHistorySet . toArray ( new String [ 0 ] ) ;
for ( int i = MAX_SEARCH_HISTORY ; i < historyArray . length ; i + + ) {
orderedHistorySet . remove ( historyArray [ i ] ) ;
}
}
// 保存到偏好设置
prefs . edit ( ) . putStringSet ( PREFERENCE_SEARCH_HISTORY , orderedHistorySet ) . apply ( ) ;
}
/ * *
* 获 取 搜 索 历 史
* @return 搜 索 历 史 集 合
* /
private Set < String > getSearchHistory ( ) {
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( this ) ;
return prefs . getStringSet ( PREFERENCE_SEARCH_HISTORY , new HashSet < String > ( ) ) ;
}
/ * *
* 清 除 搜 索 历 史
* /
private void clearSearchHistory ( ) {
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( this ) ;
prefs . edit ( ) . remove ( PREFERENCE_SEARCH_HISTORY ) . apply ( ) ;
}
/ * *
* 添 加 退 出 搜 索 模 式 的 菜 单 项
* @param menu 菜 单 对 象
* @return 是 否 成 功 创 建 菜 单
* /
@Override
public boolean onCreateOptionsMenu ( Menu menu ) {
getMenuInflater ( ) . inflate ( R . menu . note_list_options , menu ) ;
// 在搜索模式下添加退出搜索菜单项
if ( mIsSearchMode ) {
MenuItem exitSearchItem = menu . add ( Menu . NONE , R . id . menu_exit_search , Menu . NONE , "退出搜索" ) ;
exitSearchItem . setIcon ( android . R . drawable . ic_menu_close_clear_cancel ) ;
exitSearchItem . setShowAsAction ( MenuItem . SHOW_AS_ACTION_IF_ROOM ) ;
}
return true ;
}
private void exportNoteToText ( ) {
final BackupUtils backup = BackupUtils . getInstance ( NotesListActivity . this ) ;