lixing
lixing 9 months ago
parent 4f54011353
commit 4b53318c0c

@ -0,0 +1,962 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义类的包名
import android.app.Activity; // 导入Activity类
import android.app.AlertDialog; // 导入AlertDialog类
import android.app.Dialog; // 导入Dialog类
import android.appwidget.AppWidgetManager; // 导入AppWidgetManager类
import android.content.AsyncQueryHandler; // 导入AsyncQueryHandler类
import android.content.ContentResolver; // 导入ContentResolver类
import android.content.ContentValues; // 导入ContentValues类
import android.content.Context; // 导入Context类
import android.content.DialogInterface; // 导入DialogInterface类
import android.content.Intent; // 导入Intent类
import android.content.SharedPreferences; // 导入SharedPreferences类
import android.content.res.Resources; // 导入Resources类
import android.database.Cursor; // 导入Cursor类
import android.os.AsyncTask; // 导入AsyncTask类
import android.os.Bundle; // 导入Bundle类
import android.preference.PreferenceManager; // 导入PreferenceManager类
import android.text.Editable; // 导入Editable类
import android.text.TextUtils; // 导入TextUtils类
import android.text.TextWatcher; // 导入TextWatcher类
import android.util.Log; // 导入Log类
import android.view.ActionMode; // 导入ActionMode类
import android.view.ContextMenu; // 导入ContextMenu类
import android.view.Display; // 导入Display类
import android.view.HapticFeedbackConstants; // 导入HapticFeedbackConstants类
import android.view.LayoutInflater; // 导入LayoutInflater类
import android.view.Menu; // 导入Menu类
import android.view.MenuItem; // 导入MenuItem类
import android.view.MotionEvent; // 导入MotionEvent类
import android.view.View; // 导入View类
import android.view.inputmethod.InputMethodManager; // 导入InputMethodManager类
import android.widget.AdapterView; // 导入AdapterView类
import android.widget.Button; // 导入Button类
import android.widget.EditText; // 导入EditText类
import android.widget.ListView; // 导入ListView类
import android.widget.PopupMenu; // 导入PopupMenu类
import android.widget.TextView; // 导入TextView类
import android.widget.Toast; // 导入Toast类
import net.micode.notes.R; // 导入项目的资源文件
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列定义
import net.micode.notes.gtask.remote.GTaskSyncService; // 导入GTaskSyncService类
import net.micode.notes.model.WorkingNote; // 导入WorkingNote类
import net.micode.notes.tool.BackupUtils; // 导入BackupUtils类
import net.micode.notes.tool.DataUtils; // 导入DataUtils类
import net.micode.notes.tool.ResourceParser; // 导入ResourceParser类
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; // 导入AppWidgetAttribute类
import net.micode.notes.widget.NoteWidgetProvider_2x; // 导入NoteWidgetProvider_2x类
import net.micode.notes.widget.NoteWidgetProvider_4x; // 导入NoteWidgetProvider_4x类
import java.io.BufferedReader; // 导入BufferedReader类
import java.io.IOException; // 导入IOException类
import java.io.InputStream; // 导入InputStream类
import java.io.InputStreamReader; // 导入InputStreamReader类
import java.util.HashSet; // 导入HashSet类
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 定义查询令牌
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
// 定义菜单项
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 定义偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 定义枚举类型,表示列表编辑状态
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
// 初始化成员变量
private ListEditState mState; // 列表编辑状态
private BackgroundQueryHandler mBackgroundQueryHandler; // 后台查询处理器
private NotesListAdapter mNotesListAdapter; // 笔记列表适配器
private ListView mNotesListView; // 笔记列表视图
private Button mAddNewNote; // 添加新笔记按钮
private boolean mDispatch; // 是否分发事件
private int mOriginY; // 原始Y坐标
private int mDispatchY; // 分发Y坐标
private TextView mTitleBar; // 标题栏
private long mCurrentFolderId; // 当前文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 模式回调
private static final String TAG = "NotesListActivity"; // 日志标签
// 定义常量
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem; // 焦点笔记数据项
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
// 定义请求码
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 调用父类的onCreate方法
setContentView(R.layout.note_list); // 设置内容视图为note_list布局
initResources(); // 初始化资源
/**
* 使
*/
setAppInfoFromRawRes(); // 从raw资源文件中设置应用信息
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK // 如果结果码为OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { // 如果请求码为打开节点或新建节点
mNotesListAdapter.changeCursor(null); // 更改适配器的游标
} else {
super.onActivityResult(requestCode, resultCode, data); // 调用父类的onActivityResult方法
}
}
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); // 获取默认的SharedPreferences
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { // 如果没有添加过介绍
StringBuilder sb = new StringBuilder(); // 创建字符串构建器
InputStream in = null; // 创建输入流
try {
in = getResources().openRawResource(R.raw.introduction); // 打开raw资源文件
if (in != null) { // 如果输入流不为空
InputStreamReader isr = new InputStreamReader(in); // 创建输入流读取器
BufferedReader br = new BufferedReader(isr); // 创建缓冲读取器
char [] buf = new char[1024]; // 创建缓冲区
int len = 0; // 初始化长度
while ((len = br.read(buf)) > 0) { // 读取数据
sb.append(buf, 0, len); // 追加到字符串构建器
}
} else {
Log.e(TAG, "Read introduction file error"); // 日志记录错误
return;
}
} catch (IOException e) {
e.printStackTrace(); // 打印异常堆栈
return;
} finally {
if(in != null) { // 如果输入流不为空
try {
in.close(); // 关闭输入流
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); // 打印异常堆栈
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, // 创建空笔记
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString()); // 设置笔记文本
if (note.saveNote()) { // 如果保存笔记成功
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); // 设置已添加介绍
} else {
Log.e(TAG, "Save introduction note error"); // 日志记录错误
return;
}
}
}
}
@Override
protected void onStart() {
super.onStart(); // 调用父类的onStart方法
startAsyncNotesListQuery(); // 启动异步笔记列表查询
}
private void initResources() {
mContentResolver = this.getContentResolver(); // 获取内容解析器
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mNotesListView = (ListView) findViewById(R.id.notes_list); // 获取笔记列表视图
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); // 添加列表底部视图
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击监听器
mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按监听器
mNotesListAdapter = new NotesListAdapter(this); // 创建笔记列表适配器
mNotesListView.setAdapter(mNotesListAdapter); // 设置列表适配器
mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 获取添加新笔记按钮
mAddNewNote.setOnClickListener(this); // 设置按钮点击监听器
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置按钮触摸监听器
mDispatch = false; // 初始化分发标志为false
mDispatchY = 0; // 初始化分发Y坐标为0
mOriginY = 0; // 初始化原始Y坐标为0
mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 获取标题栏
mState = ListEditState.NOTE_LIST; // 初始化列表编辑状态为NOTE_LIST
mModeCallBack = new ModeCallback(); // 创建模式回调
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu; // 下拉菜单
private ActionMode mActionMode; // 动作模式
private MenuItem mMoveMenu; // 移动菜单项
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 填充动作模式菜单
getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
// 根据是否有文件夹和是否为通话记录文件夹设置移动菜单项的可见性
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode; // 设置动作模式
mNotesListAdapter.setChoiceMode(true); // 设置列表选择模式为true
mNotesListView.setLongClickable(false); // 设置列表不可长按
mAddNewNote.setVisibility(View.GONE); // 隐藏添加新笔记按钮
// 填充自定义视图
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem item) {
// 切换全选状态
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu();
return true;
}
});
return true;
}
private void updateMenu() {
// 更新菜单项
int selectedCount = mNotesListAdapter.getSelectedCount();
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false); // 设置列表选择模式为false
mNotesListView.setLongClickable(true); // 设置列表可长按
mAddNewNote.setVisibility(View.VISIBLE); // 显示添加新笔记按钮
}
public void finishActionMode() {
mActionMode.finish(); // 结束动作模式
}
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked); // 设置列表项的选中状态
updateMenu(); // 更新菜单
}
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show(); // 提示未选择任何项
return true;
}
switch (item.getItemId()) {
case R.id.delete:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete(); // 批量删除
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
startQueryDestinationFolders(); // 查询目标文件夹
break;
default:
return false;
}
return true;
}
}
private class NewNoteOnTouchListener implements OnTouchListener {
// 实现触摸监听器,处理“添加新笔记”按钮的触摸事件
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 获取屏幕高度和“添加新笔记”按钮的高度
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
// 计算触摸事件的Y坐标
int eventY = start + (int) event.getY();
// 如果处于子文件夹状态,则减去标题栏的高度
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
// 如果触摸事件在“添加新笔记”按钮的透明部分,则将事件分发到列表视图
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event); // 分发触摸事件
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
// 在移动事件中更新分发的Y坐标
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event); // 分发触摸事件
}
break;
}
default: {
// 在其他事件中,重置分发标志
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event); // 分发触摸事件
}
break;
}
}
return false; // 返回false表示未处理事件
}
}
private void startAsyncNotesListQuery() {
// 根据当前文件夹ID选择查询条件
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 启动异步查询,查询笔记列表
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId) // 添加当前文件夹ID作为查询参数
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); // 按类型和修改日期降序排列
}
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver); // 调用父类构造函数
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor); // 更新笔记列表适配器的游标
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
showFolderListMenu(cursor); // 显示文件夹列表菜单
} else {
Log.e(TAG, "Query folder failed"); // 日志记录查询文件夹失败
}
break;
default:
return; // 处理其他令牌
}
}
}
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建对话框构建器
builder.setTitle(R.string.menu_title_select_folder); // 设置对话框标题
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); // 创建文件夹列表适配器
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 移动选中的笔记到所选文件夹
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
// 显示移动成功的提示
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode(); // 结束动作模式
}
});
builder.show(); // 显示对话框
}
private void createNewNote() {
// 创建新的笔记编辑意图
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置动作为插入或编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); // 添加当前文件夹ID
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); // 启动新笔记编辑活动
}
private void batchDelete() {
// 创建异步任务以批量删除笔记
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget(); // 获取选中小部件
if (!isSyncMode()) { // 如果不是同步模式
// 直接删除笔记
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
// 删除成功
} else {
Log.e(TAG, "Delete notes error, should not happens"); // 日志记录删除错误
}
} else {
// 在同步模式下,将删除的笔记移动到垃圾文件夹
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens"); // 日志记录移动错误
}
}
return widgets; // 返回小部件集合
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) { // 如果小部件集合不为空
for (AppWidgetAttribute widget : widgets) { // 遍历小部件
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
mModeCallBack.finishActionMode(); // 结束动作模式
}
}.execute(); // 执行异步任务
}
private void deleteFolder(long folderId) {
// 如果文件夹ID是根文件夹ID记录错误并返回
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<Long>(); // 创建文件夹ID集合
ids.add(folderId); // 添加文件夹ID
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId); // 获取与文件夹相关的小部件
if (!isSyncMode()) { // 如果不是同步模式
// 直接删除文件夹
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// 在同步模式下,将删除的文件夹移动到垃圾文件夹
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
if (widgets != null) { // 如果小部件集合不为空
for (AppWidgetAttribute widget : widgets) { // 遍历小部件
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
}
private void openNode(NoteItemData data) {
// 创建打开笔记的意图
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW); // 设置动作为查看
intent.putExtra(Intent.EXTRA_UID, data.getId()); // 添加笔记ID到意图
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); // 启动笔记编辑活动
}
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId(); // 更新当前文件夹ID
startAsyncNotesListQuery(); // 启动异步笔记列表查询
// 根据文件夹ID设置状态
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mState = ListEditState.CALL_RECORD_FOLDER; // 设置状态为通话记录文件夹
mAddNewNote.setVisibility(View.GONE); // 隐藏添加新笔记按钮
} else {
mState = ListEditState.SUB_FOLDER; // 设置状态为子文件夹
}
// 更新标题栏文本
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name); // 设置标题为通话记录文件夹名称
} else {
mTitleBar.setText(data.getSnippet()); // 设置标题为文件夹摘要
}
mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note: // 如果点击的是添加新笔记按钮
createNewNote(); // 创建新笔记
break;
default:
break; // 其他情况不处理
}
}
private void showSoftInput() {
// 显示软键盘
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); // 强制显示软键盘
}
}
private void hideSoftInput(View view) {
// 隐藏软键盘
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showCreateOrModifyFolderDialog(final boolean create) {
// 显示创建或修改文件夹的对话框
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput(); // 显示软键盘
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet()); // 设置对话框中编辑框的文本
builder.setTitle(getString(R.string.menu_folder_change_name)); // 设置对话框标题为“修改文件夹名称”
} else {
Log.e(TAG, "The long click data item is null"); // 日志记录错误
return;
}
} else {
etName.setText(""); // 清空编辑框文本
builder.setTitle(this.getString(R.string.menu_create_folder)); // 设置对话框标题为“创建文件夹”
}
builder.setPositiveButton(android.R.string.ok, null); // 设置对话框的“确定”按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName); // 隐藏软键盘
}
});
final Dialog dialog = builder.setView(view).show(); // 显示对话框
final Button positive = (Button)dialog.findViewById(android.R.id.button1); // 获取“确定”按钮
positive.setOnClickListener(new OnClickListener() { // 设置“确定”按钮的点击事件
public void onClick(View v) {
hideSoftInput(etName); // 隐藏软键盘
String name = etName.getText().toString(); // 获取编辑框的文本
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { // 检查文件夹名称是否已存在
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show(); // 显示提示
etName.setSelection(0, etName.length()); // 将光标移动到文本末尾
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) { // 如果不是创建新文件夹且名称不为空
ContentValues values = new ContentValues(); // 创建ContentValues对象
values.put(NoteColumns.SNIPPET, name); // 更新文件夹名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 更新文件夹类型
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 更新本地修改标志
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] { String.valueOf(mFocusNoteDataItem.getId()) }); // 更新数据库
}
} else if (!TextUtils.isEmpty(name)) { // 如果是创建新文件夹且名称不为空
ContentValues values = new ContentValues(); // 创建ContentValues对象
values.put(NoteColumns.SNIPPET, name); // 设置文件夹名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置文件夹类型
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 插入数据库
}
dialog.dismiss(); // 关闭对话框
}
});
if (TextUtils.isEmpty(etName.getText())) { // 如果编辑框文本为空
positive.setEnabled(false); // 禁用“确定”按钮
}
// 当编辑框文本变化时,启用或禁用“确定”按钮
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false); // 禁用“确定”按钮
} else {
positive.setEnabled(true); // 启用“确定”按钮
}
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
@Override
public void onBackPressed() {
// 处理返回键事件
switch (mState) {
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mState = ListEditState.NOTE_LIST; // 设置状态为NOTE_LIST
startAsyncNotesListQuery(); // 启动异步笔记列表查询
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
break;
case CALL_RECORD_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mState = ListEditState.NOTE_LIST; // 设置状态为NOTE_LIST
mAddNewNote.setVisibility(View.VISIBLE); // 显示添加新笔记按钮
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
startAsyncNotesListQuery(); // 启动异步笔记列表查询
break;
case NOTE_LIST:
super.onBackPressed(); // 调用父类的onBackPressed方法
break;
default:
break;
}
}
private void updateWidget(int appWidgetId, int appWidgetType) {
// 创建更新小部件的意图
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据小部件类型设置小部件提供者类
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type"); // 日志记录不支持的小部件类型
return; // 返回
}
// 添加小部件ID到意图
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
sendBroadcast(intent); // 发送广播以更新小部件
setResult(RESULT_OK, intent); // 设置结果为OK
}
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
// 创建上下文菜单
if (mFocusNoteDataItem != null) { // 如果焦点笔记数据项不为空
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为笔记摘要
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); // 添加查看菜单项
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); // 添加删除菜单项
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); // 添加修改名称菜单项
}
}
};
@Override
public void onContextMenuClosed(Menu menu) {
// 当上下文菜单关闭时
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null); // 清除列表视图的上下文菜单监听器
}
super.onContextMenuClosed(menu); // 调用父类的onContextMenuClosed方法
}
@Override
public boolean onContextItemSelected(MenuItem item) {
// 处理上下文菜单项的选择
if (mFocusNoteDataItem == null) { // 如果焦点笔记数据项为空
Log.e(TAG, "The long click data item is null"); // 日志记录错误
return false; // 返回false
}
switch (item.getItemId()) { // 根据菜单项ID执行相应操作
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem); // 打开文件夹
break;
case MENU_FOLDER_DELETE:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert); // 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_folder)); // 设置对话框消息
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId()); // 删除文件夹
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮
builder.show(); // 显示对话框
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false); // 显示修改文件夹名称对话框
break;
default:
break; // 其他情况不处理
}
return true; // 返回true表示菜单项已处理
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// 准备选项菜单
menu.clear(); // 清除菜单项
if (mState == ListEditState.NOTE_LIST) { // 如果当前状态是笔记列表
getMenuInflater().inflate(R.menu.note_list, menu); // 加载笔记列表菜单
// 设置同步或取消同步的菜单项标题
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) { // 如果当前状态是子文件夹
getMenuInflater().inflate(R.menu.sub_folder, menu); // 加载子文件夹菜单
} else if (mState == ListEditState.CALL_RECORD_FOLDER) { // 如果当前状态是通话记录文件夹
getMenuInflater().inflate(R.menu.call_record_folder, menu); // 加载通话记录文件夹菜单
} else {
Log.e(TAG, "Wrong state:" + mState); // 日志记录错误状态
}
return true; // 返回true表示菜单已准备好
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 处理选项菜单项的选择
switch (item.getItemId()) {
case R.id.menu_new_folder: { // 如果点击的是新建文件夹菜单项
showCreateOrModifyFolderDialog(true); // 显示创建文件夹对话框
break;
}
case R.id.menu_export_text: { // 如果点击的是导出文本菜单项
exportNoteToText(); // 导出笔记为文本
break;
}
case R.id.menu_sync: { // 如果点击的是同步菜单项
if (isSyncMode()) { // 如果处于同步模式
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this); // 开始同步
} else {
GTaskSyncService.cancelSync(this); // 取消同步
}
} else {
startPreferenceActivity(); // 启动偏好设置活动
}
break;
}
case R.id.menu_setting: { // 如果点击的是设置菜单项
startPreferenceActivity(); // 启动偏好设置活动
break;
}
case R.id.menu_new_note: { // 如果点击的是新建笔记菜单项
createNewNote(); // 创建新笔记
break;
}
case R.id.menu_search: // 如果点击的是搜索菜单项
onSearchRequested(); // 请求搜索
break;
default:
break; // 其他情况不处理
}
return true; // 返回true表示菜单项已处理
}
@Override
public boolean onSearchRequested() {
// 请求搜索
startSearch(null, false, null /* appData */, false); // 启动搜索
return true; // 返回true表示搜索请求已处理
}
private void exportNoteToText() {
// 使用备份工具实例导出笔记到文本
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
// 后台任务:调用备份工具的导出方法
return backup.exportToText();
}
@Override
protected void onPostExecute(Integer result) {
// 根据导出结果弹出不同的对话框
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
// SD卡未挂载
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
// 导出成功
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export));
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
// 系统错误
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute(); // 执行异步任务
}
private boolean isSyncMode() {
// 检查是否处于同步模式
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
private void startPreferenceActivity() {
// 启动偏好设置活动
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 处理列表项点击事件
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
// 处理多选模式下的点击事件
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
// 根据当前状态处理点击事件
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER
|| item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item); // 打开文件夹
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
default:
break;
}
}
}
}
private void startQueryDestinationFolders() {
// 查询目标文件夹
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 处理列表项长按事件
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
// 启动动作模式
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
return false;
}

@ -0,0 +1,195 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义类的包名
import android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.util.Log; // 导入Log类
import android.view.View; // 导入View类
import android.view.ViewGroup; // 导入ViewGroup类
import android.widget.CursorAdapter; // 导入CursorAdapter类
import net.micode.notes.data.Notes; // 导入Notes类
import java.util.Collection; // 导入Collection接口
import java.util.HashMap; // 导入HashMap类
import java.util.HashSet; // 导入HashSet类
import java.util.Iterator; // 导入Iterator接口
// 自定义的笔记列表适配器类继承自CursorAdapter
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter"; // 定义日志标签
private Context mContext; // 上下文对象
private HashMap<Integer, Boolean> mSelectedIndex; // 存储选中状态的哈希映射
private int mNotesCount; // 笔记计数
private boolean mChoiceMode; // 是否处于选择模式
// 定义AppWidgetAttribute内部类用于存储小部件属性
public static class AppWidgetAttribute {
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
};
// 构造函数,接收上下文对象
public NotesListAdapter(Context context) {
super(context, null); // 调用父类构造函数
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化选中状态哈希映射
mContext = context; // 初始化上下文对象
mNotesCount = 0; // 初始化笔记计数为0
}
// 新建视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context); // 返回新的NotesListItem视图
}
// 绑定视图
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) { // 如果视图是NotesListItem的实例
NoteItemData itemData = new NoteItemData(context, cursor); // 创建NoteItemData对象
((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition())); // 绑定数据到视图
}
}
// 设置选中项
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked); // 将位置和选中状态放入哈希映射
notifyDataSetChanged(); // 通知数据集改变
}
// 判断是否处于选择模式
public boolean isInChoiceMode() {
return mChoiceMode; // 返回选择模式状态
}
// 设置选择模式
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); // 清除选中状态哈希映射
mChoiceMode = mode; // 设置选择模式状态
}
// 全选或全不选
public void selectAll(boolean checked) {
Cursor cursor = getCursor(); // 获取游标
for (int i = 0; i < getCount(); i++) { // 遍历所有项
if (cursor.moveToPosition(i)) { // 移动到当前位置
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { // 如果是笔记类型
setCheckedItem(i, checked); // 设置选中状态
}
}
}
}
// 获取选中的笔记ID集合
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>(); // 创建ID集合
for (Integer position : mSelectedIndex.keySet()) { // 遍历选中状态哈希映射
if (mSelectedIndex.get(position) == true) { // 如果选中
Long id = getItemId(position); // 获取ID
if (id == Notes.ID_ROOT_FOLDER) { // 如果是根文件夹ID
Log.d(TAG, "Wrong item id, should not happen"); // 日志记录错误
} else {
itemSet.add(id); // 添加到ID集合
}
}
}
return itemSet; // 返回ID集合
}
// 获取选中的小部件属性集合
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); // 创建小部件属性集合
for (Integer position : mSelectedIndex.keySet()) { // 遍历选中状态哈希映射
if (mSelectedIndex.get(position) == true) { // 如果选中
Cursor c = (Cursor) getItem(position); // 获取游标
if (c != null) { // 如果游标不为空
AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建小部件属性对象
NoteItemData item = new NoteItemData(mContext, c); // 创建NoteItemData对象
widget.widgetId = item.getWidgetId(); // 获取小部件ID
widget.widgetType = item.getWidgetType(); // 获取小部件类型
itemSet.add(widget); // 添加到小部件属性集合
} else {
Log.e(TAG, "Invalid cursor"); // 日志记录错误
return null; // 返回null
}
}
}
return itemSet; // 返回小部件属性集合
}
}
public int getSelectedCount() {
// 获取选中项的数量
Collection<Boolean> values = mSelectedIndex.values(); // 获取选中状态值的集合
if (null == values) {
return 0; // 如果集合为空则返回0
}
Iterator<Boolean> iter = values.iterator(); // 创建迭代器
int count = 0; // 初始化计数器
while (iter.hasNext()) {
if (true == iter.next()) { // 如果项被选中
count++; // 增加计数器
}
}
return count; // 返回选中项的数量
}
public boolean isAllSelected() {
// 检查是否所有项都被选中
int checkedCount = getSelectedCount(); // 获取选中项的数量
return (checkedCount != 0 && checkedCount == mNotesCount); // 如果选中项的数量大于0且等于笔记总数则返回true
}
public boolean isSelectedItem(final int position) {
// 检查指定位置的项是否被选中
if (null == mSelectedIndex.get(position)) { // 如果指定位置没有选中状态
return false; // 返回false
}
return mSelectedIndex.get(position); // 返回选中状态
}
@Override
protected void onContentChanged() {
// 当内容变化时被调用
super.onContentChanged(); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
@Override
public void changeCursor(Cursor cursor) {
// 当游标变化时被调用
super.changeCursor(cursor); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
private void calcNotesCount() {
// 计算笔记的数量
mNotesCount = 0; // 初始化笔记计数器
for (int i = 0; i < getCount(); i++) { // 遍历所有项
Cursor c = (Cursor) getItem(i); // 获取游标
if (c != null) { // 如果游标不为空
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { // 如果项是笔记类型
mNotesCount++; // 增加笔记计数器
}
} else {
Log.e(TAG, "Invalid cursor"); // 日志记录无效游标错误
return; // 返回
}
}
}

@ -0,0 +1,129 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义类的包名
import android.content.Context; // 导入上下文类
import android.text.format.DateUtils; // 导入日期工具类
import android.view.View; // 导入视图类
import android.widget.CheckBox; // 导入复选框类
import android.widget.ImageView; // 导入图像视图类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R; // 导入项目的资源文件
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.tool.DataUtils; // 导入数据工具类
import net.micode.notes.tool.ResourceParser.NoteItemBgResources; // 导入笔记项背景资源解析器类
// 自定义的笔记列表项类继承自LinearLayout
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 警告图标视图
private TextView mTitle; // 笔记标题文本视图
private TextView mTime; // 笔记时间文本视图
private TextView mCallName; // 通话记录联系人名称文本视图
private NoteItemData mItemData; // 笔记项数据
private CheckBox mCheckBox; // 复选框视图
// 构造函数,接收上下文
public NotesListItem(Context context) {
super(context); // 调用父类构造函数
inflate(context, R.layout.note_item, this); // 从布局文件中填充视图
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 获取警告图标视图
mTitle = (TextView) findViewById(R.id.tv_title); // 获取标题文本视图
mTime = (TextView) findViewById(R.id.tv_time); // 获取时间文本视图
mCallName = (TextView) findViewById(R.id.tv_name); // 获取联系人名称文本视图
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 获取复选框视图
}
// 绑定数据到视图
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) { // 如果处于选择模式且数据类型为笔记
mCheckBox.setVisibility(View.VISIBLE); // 显示复选框
mCheckBox.setChecked(checked); // 设置复选框状态
} else {
mCheckBox.setVisibility(View.GONE); // 隐藏复选框
}
mItemData = data; // 绑定笔记项数据
// 如果是通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE); // 隐藏联系人名称
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题文本外观
mTitle.setText(context.getString(R.string.call_record_folder_name) // 设置标题为通话记录文件夹名称
+ context.getString(R.string.format_folder_files_count, data.getNotesCount())); // 添加文件数量
mAlert.setImageResource(R.drawable.call_record); // 设置警告图标为通话记录图标
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果父ID是通话记录文件夹
mCallName.setVisibility(View.VISIBLE); // 显示联系人名称
mCallName.setText(data.getCallName()); // 设置联系人名称
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem); // 设置标题文本外观
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题为格式化的摘要
// 根据是否有提醒设置警告图标的可见性
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); // 设置警告图标为时钟图标
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
} else {
mAlert.setVisibility(View.GONE); // 隐藏警告图标
}
} else { // 如果是普通笔记或文件夹
mCallName.setVisibility(View.GONE); // 隐藏联系人名称
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题文本外观
if (data.getType() == Notes.TYPE_FOLDER) { // 如果是文件夹类型
mTitle.setText(data.getSnippet() // 设置标题为文件夹摘要
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount())); // 添加文件数量
mAlert.setVisibility(View.GONE); // 隐藏警告图标
} else { // 如果是普通笔记类型
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题为格式化的摘要
// 根据是否有提醒设置警告图标的可见性
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); // 设置警告图标为时钟图标
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
} else {
mAlert.setVisibility(View.GONE); // 隐藏警告图标
}
}
}
// 设置时间文本为相对时间
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); // 设置时间为相对时间
setBackground(data); // 设置背景
}
private void setBackground(NoteItemData data) {
// 根据笔记类型和状态设置背景
int id = data.getBgColorId(); // 获取背景颜色ID
if (data.getType() == Notes.TYPE_NOTE) { // 如果是笔记类型
if (data.isSingle() || data.isOneFollowingFolder()) { // 如果是单个笔记或跟随文件夹的单个笔记
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); // 设置单个笔记背景
} else if (data.isLast()) { // 如果是最后一项
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); // 设置最后一项背景
} else if (data.isFirst() || data.isMultiFollowingFolder()) { // 如果是第一项或跟随文件夹的多个笔记
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); // 设置第一项背景
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); // 设置普通背景
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes()); // 如果是文件夹类型,设置文件夹背景
}
}
public NoteItemData getItemData() {
// 返回笔记项数据
return mItemData;
}
}

@ -0,0 +1,465 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义类的包名
import android.accounts.Account; // 导入Account类
import android.accounts.AccountManager; // 导入AccountManager类
import android.app.ActionBar; // 导入ActionBar类
import android.app.AlertDialog; // 导入AlertDialog类
import android.content.BroadcastReceiver; // 导入BroadcastReceiver类
import android.content.ContentValues; // 导入ContentValues类
import android.content.Context; // 导入Context类
import android.content.DialogInterface; // 导入DialogInterface类
import android.content.Intent; // 导入Intent类
import android.content.IntentFilter; // 导入IntentFilter类
import android.content.SharedPreferences; // 导入SharedPreferences类
import android.os.Bundle; // 导入Bundle类
import android.preference.Preference; // 导入Preference类
import android.preference.Preference.OnPreferenceClickListener; // 导入PreferenceClickListener接口
import android.preference.PreferenceActivity; // 导入PreferenceActivity类
import android.preference.PreferenceCategory; // 导入PreferenceCategory类
import android.text.TextUtils; // 导入TextUtils类
import android.text.format.DateFormat; // 导入DateFormat类
import android.view.LayoutInflater; // 导入LayoutInflater类
import android.view.Menu; // 导入Menu类
import android.view.MenuItem; // 导入MenuItem类
import android.view.View; // 导入View类
import android.widget.Button; // 导入Button类
import android.widget.TextView; // 导入TextView类
import android.widget.Toast; // 导入Toast类
import net.micode.notes.R; // 导入项目的资源文件
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类
import net.micode.notes.gtask.remote.GTaskSyncService; // 导入GTaskSyncService类
// 继承自PreferenceActivity的设置活动类
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences"; // 设置的名称
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账户名称的键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 最后同步时间的键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 设置背景颜色的键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账户的键
private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 权限过滤的键
private PreferenceCategory mAccountCategory; // 账户类别的PreferenceCategory
private GTaskReceiver mReceiver; // 用于接收GTask同步状态的BroadcastReceiver
private Account[] mOriAccounts; // 原始账户数组
private boolean mHasAddedAccount; // 是否添加了账户
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle); // 调用父类的onCreate方法
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true); // 设置ActionBar的返回键可用
addPreferencesFromResource(R.xml.preferences); // 从资源文件中添加偏好设置
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); // 获取同步账户的PreferenceCategory
mReceiver = new GTaskReceiver(); // 创建GTaskReceiver实例
IntentFilter filter = new IntentFilter(); // 创建IntentFilter
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // 添加GTask服务广播的Action
registerReceiver(mReceiver, filter); // 注册BroadcastReceiver
mOriAccounts = null; // 初始化原始账户数组为null
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); // 从布局文件中填充设置头部视图
getListView().addHeaderView(header, null, true); // 将头部视图添加到ListView
}
@Override
protected void onResume() {
super.onResume(); // 调用父类的onResume方法
// 如果用户添加了新账户,需要自动设置同步账户
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts(); // 获取Google账户数组
if (mOriAccounts != null && accounts.length > mOriAccounts.length) { // 如果新账户数组长度大于原始账户数组长度
for (Account accountNew : accounts) { // 遍历新账户数组
boolean found = false; // 初始化找到标志为false
for (Account accountOld : mOriAccounts) { // 遍历原始账户数组
if (TextUtils.equals(accountOld.name, accountNew.name)) { // 如果找到相同的账户
found = true; // 设置找到标志为true
break; // 跳出循环
}
}
if (!found) { // 如果没有找到相同的账户
setSyncAccount(accountNew.name); // 设置同步账户
break; // 跳出循环
}
}
}
}
refreshUI(); // 刷新用户界面
}
}
@Override
protected void onDestroy() {
// 检查mReceiver是否已经被实例化如果是则注销广播接收器
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
// 调用父类的onDestroy方法继续执行其他清理工作
super.onDestroy();
}
// 加载账户偏好设置
private void loadAccountPreference() {
// 清除mAccountCategory中的所有Preference
mAccountCategory.removeAll();
// 创建一个新的Preference对象
Preference accountPref = new Preference(this);
// 获取默认同步账户的名称
final String defaultAccount = getSyncAccountName(this);
// 设置Preference的标题
accountPref.setTitle(getString(R.string.preferences_account_title));
// 设置Preference的摘要信息
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置Preference点击事件的监听器
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// 如果没有正在进行的同步操作
if (!GTaskSyncService.isSyncing()) {
// 如果defaultAccount为空表示是第一次设置账户
if (TextUtils.isEmpty(defaultAccount)) {
showSelectAccountAlertDialog(); // 显示选择账户的AlertDialog
} else {
// 如果账户已经设置,提示用户更换账户的风险
showChangeAccountConfirmAlertDialog();
}
} else {
// 如果正在进行同步操作显示一个Toast提示用户不能更改账户
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
// 返回true表示已经处理了点击事件
return true;
}
});
// 将创建的Preference添加到mAccountCategory中
mAccountCategory.addPreference(accountPref);
}
// 加载同步按钮
private void loadSyncButton() {
// 通过findViewById获取同步按钮和最后同步时间的TextView
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 根据是否正在同步来设置按钮的状态
if (GTaskSyncService.isSyncing()) {
// 如果正在同步,设置按钮文本为取消同步,并设置点击事件为取消同步操作
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 取消同步
}
});
} else {
// 如果没有同步操作,设置按钮文本为立即同步,并设置点击事件为开始同步操作
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this); // 开始同步
}
});
}
// 根据账户名称是否为空来启用或禁用同步按钮
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 设置最后同步时间的显示
if (GTaskSyncService.isSyncing()) {
// 如果正在同步,显示同步进度
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
// 如果没有同步操作,显示最后同步时间
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
// 刷新UI加载账户偏好和同步按钮
private void refreshUI() {
loadAccountPreference(); // 加载账户偏好
loadSyncButton(); // 加载同步按钮
}
// 显示选择账户的对话框
private void showSelectAccountAlertDialog() {
// 创建AlertDialog.Builder对象用于构建对话框
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 从布局文件中加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 获取标题TextView并设置标题文本
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
// 获取副标题TextView并设置副标题文本
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
// 设置自定义标题
dialogBuilder.setCustomTitle(titleView);
// 设置正面按钮(未设置文本和点击事件,后续会处理)
dialogBuilder.setPositiveButton(null, null);
// 获取Google账户列表
Account[] accounts = getGoogleAccounts();
// 获取当前同步账户名称
String defAccount = getSyncAccountName(this);
// 保存原始账户列表和添加账户标志
mOriAccounts = accounts;
mHasAddedAccount = false;
// 如果有账户可供选择
if (accounts.length > 0) {
// 创建CharSequence数组用于存储账户名称
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items; // 用于后续选择
int checkedItem = -1; // 记录选中的账户索引
int index = 0; // 当前索引
// 遍历账户列表
for (Account account : accounts) {
// 如果账户名称与默认账户名称匹配,记录选中的索引
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
// 将账户名称添加到items数组中
items[index++] = account.name;
}
// 设置单选列表,用户可以选择账户
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 设置选中的同步账户
setSyncAccount(itemMapping[which].toString());
dialog.dismiss(); // 关闭对话框
refreshUI(); // 刷新UI
}
});
}
// 从布局文件中加载添加账户的视图
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
// 将添加账户视图设置为对话框的内容视图
dialogBuilder.setView(addAccountView);
// 显示对话框
final AlertDialog dialog = dialogBuilder.show();
// 设置点击添加账户视图的事件
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true; // 设置已添加账户标志
// 创建意图以打开添加账户设置
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls" // 过滤器,指定要添加的账户类型
});
startActivityForResult(intent, -1); // 启动添加账户设置
dialog.dismiss(); // 关闭对话框
}
});
}
// 显示更改账户确认对话框
private void showChangeAccountConfirmAlertDialog() {
// 创建AlertDialog.Builder对象
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 从布局文件中加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 获取标题TextView并设置标题文本
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this))); // 显示当前同步账户名称
// 获取副标题TextView并设置副标题文本
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
// 设置自定义标题
dialogBuilder.setCustomTitle(titleView);
// 创建菜单项数组
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account), // 更改账户
getString(R.string.preferences_menu_remove_account), // 移除账户
getString(R.string.preferences_menu_cancel) // 取消
};
// 设置菜单项的点击事件
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 根据用户选择的菜单项执行相应操作
if (which == 0) {
showSelectAccountAlertDialog(); // 显示选择账户对话框
} else if (which == 1) {
removeSyncAccount(); // 移除同步账户
refreshUI(); // 刷新UI
}
}
});
// 显示对话框
dialogBuilder.show();
}
// 获取Google账户列表
private Account[] getGoogleAccounts() {
// 获取AccountManager实例
AccountManager accountManager = AccountManager.get(this);
// 返回所有Google账户
return accountManager.getAccountsByType("com.google");
}
// 设置同步账户
private void setSyncAccount(String account) {
// 检查当前同步账户是否与传入的账户不同
if (!getSyncAccountName(this).equals(account)) {
// 获取SharedPreferences实例用于存储偏好设置
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果传入的账户不为空,则保存账户名称;否则保存空字符串
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
editor.commit(); // 提交更改
// 清除最后同步时间
setLastSyncTime(this, 0);
// 清除本地与gtask相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清空GTASK_ID
values.put(NoteColumns.SYNC_ID, 0); // 清空SYNC_ID
// 更新Notes.CONTENT_NOTE_URI中的数据
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start(); // 启动新线程执行清理操作
// 显示成功设置账户的Toast提示
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
// 移除同步账户
private void removeSyncAccount() {
// 获取SharedPreferences实例
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果存在同步账户名称,则移除
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 如果存在最后同步时间,则移除
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit(); // 提交更改
// 清除本地与gtask相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清空GTASK_ID
values.put(NoteColumns.SYNC_ID, 0); // 清空SYNC_ID
// 更新Notes.CONTENT_NOTE_URI中的数据
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start(); // 启动新线程执行清理操作
}
// 获取当前同步账户名称
public static String getSyncAccountName(Context context) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 返回存储的同步账户名称,默认为空字符串
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置最后同步时间
public static void setLastSyncTime(Context context, long time) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 保存最后同步时间
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit(); // 提交更改
}
// 获取最后同步时间
public static long getLastSyncTime(Context context) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 返回存储的最后同步时间默认为0
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 定义GTaskReceiver类继承自BroadcastReceiver
private class GTaskReceiver extends BroadcastReceiver {
// 当接收到广播时调用
@Override
public void onReceive(Context context, Intent intent) {
refreshUI(); // 刷新UI
// 检查广播中是否包含同步状态信息
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 获取同步状态的TextView并更新其文本
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 处理选项菜单项的选择事件
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: // 如果点击的是返回按钮
// 创建返回到NotesListActivity的意图
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清除上层活动
startActivity(intent); // 启动活动
return true; // 返回true表示事件已处理
default:
return false; // 返回false表示未处理该事件
}
}
Loading…
Cancel
Save