diff --git a/doc/用例顺序图.pdf b/doc/用例顺序图.pdf new file mode 100644 index 0000000..7b91897 Binary files /dev/null and b/doc/用例顺序图.pdf differ diff --git a/src/model/Note.java b/src/model/Note.java index 6706cf6..09526ac 100644 --- a/src/model/Note.java +++ b/src/model/Note.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -33,27 +33,36 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; - +// Note类表示一个便签,包含便签的数据和与数据库交互的方法 public class Note { + // 存储便签变化的ContentValues对象 private ContentValues mNoteDiffValues; + // 存储便签数据的NoteData对象 private NoteData mNoteData; + // 日志标签 private static final String TAG = "Note"; + /** - * Create a new note id for adding a new note to databases + * 创建一个新的便签ID,用于将新便签添加到数据库中 + * @param context 上下文对象 + * @param folderId 便签所属文件夹的ID + * @return 新创建的便签ID */ public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 创建一个新的ContentValues对象,用于存储便签数据 ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); - values.put(NoteColumns.CREATED_DATE, createdTime); - values.put(NoteColumns.MODIFIED_DATE, createdTime); - values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - values.put(NoteColumns.PARENT_ID, folderId); + values.put(NoteColumns.CREATED_DATE, createdTime); // 创建时间 + values.put(NoteColumns.MODIFIED_DATE, createdTime); // 修改时间 + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 便签类型 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 本地修改标志 + values.put(NoteColumns.PARENT_ID, folderId); // 父ID,即文件夹ID + // 将新便签插入数据库 Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); long noteId = 0; try { + // 从URI中获取新创建的便签ID noteId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); @@ -65,41 +74,50 @@ public class Note { return noteId; } + // Note类的构造函数 public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + // 设置便签的值 public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 设置修改时间 } + // 设置文本数据 public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + // 设置文本数据ID public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + // 获取文本数据ID public long getTextDataId() { return mNoteData.mTextDataId; } + // 设置通话数据ID public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + // 设置通话数据 public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + // 检查便签是否本地修改过 public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + // 同步便签到数据库 public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -110,15 +128,15 @@ public class Note { } /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info + * 理论上,一旦数据变化,便签应该在{@link NoteColumns#LOCAL_MODIFIED}和 + * {@link NoteColumns#MODIFIED_DATE}上更新。为了数据安全,即使更新便签失败,我们也应该更新 + * 便签数据信息 */ if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + // 不返回,继续执行 } mNoteDiffValues.clear(); @@ -130,15 +148,12 @@ public class Note { return true; } + // NoteData内部类,存储便签的数据 private class NoteData { private long mTextDataId; - private ContentValues mTextDataValues; - private long mCallDataId; - private ContentValues mCallDataValues; - private static final String TAG = "NoteData"; public NoteData() { @@ -148,10 +163,12 @@ public class Note { mCallDataId = 0; } + // 检查便签数据是否本地修改过 boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + // 设置文本数据ID void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +176,7 @@ public class Note { mTextDataId = id; } + // 设置通话数据ID void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,21 +184,24 @@ public class Note { mCallDataId = id; } + // 设置通话数据 void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 设置文本数据 void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 将便签数据推送到内容解析器 Uri pushIntoContentResolver(Context context, long noteId) { /** - * Check for safety + * 安全检查 */ if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -220,34 +241,4 @@ public class Note { try { setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); - mCallDataValues.clear(); - return null; - } - } else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mCallDataId)); - builder.withValues(mCallDataValues); - operationList.add(builder.build()); - } - mCallDataValues.clear(); - } - - if (operationList.size() > 0) { - try { - ContentProviderResult[] results = context.getContentResolver().applyBatch( - Notes.AUTHORITY, operationList); - return (results == null || results.length == 0 || results[0] == null) ? null - : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } - } - return null; - } - } -} + Log.e(TAG, "Insert new call data fail with noteId" + noteId); \ No newline at end of file diff --git a/src/ui(r)/NoteEditText.java b/src/ui(r)/NoteEditText.java new file mode 100644 index 0000000..392af3f --- /dev/null +++ b/src/ui(r)/NoteEditText.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权声明,表明代码版权归属于The MiCode Open Source Community。 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * 声明该文件遵循Apache License 2.0。 + * 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 + * 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; +// 声明该类属于net.micode.notes.ui包。 + +import android.content.Context; +// 导入Context类,用于获取应用程序环境信息。 +import android.graphics.Rect; +// 导入Rect类,用于处理矩形区域。 +import android.text.Layout; +// 导入Layout类,用于文本布局。 +import android.text.Selection; +// 导入Selection类,用于文本选择。 +import android.text.Spanned; +// 导入Spanned接口,用于处理富文本。 +import android.text.TextUtils; +// 导入TextUtils类,用于处理文本工具。 +import android.text.style.URLSpan; +// 导入URLSpan类,用于处理URL样式。 +import android.util.AttributeSet; +// 导入AttributeSet类,用于XML属性集。 +import android.util.Log; +// 导入Log类,用于日志输出。 +import android.view.ContextMenu; +// 导入ContextMenu类,用于上下文菜单。 +import android.view.KeyEvent; +// 导入KeyEvent类,用于处理按键事件。 +import android.view.MenuItem; +// 导入MenuItem类,用于菜单项。 +import android.view.MenuItem.OnMenuItemClickListener; +// 导入OnMenuItemClickListener接口,用于菜单项点击事件。 +import android.view.MotionEvent; +// 导入MotionEvent类,用于处理触摸事件。 +import android.widget.EditText; +// 导入EditText类,用于文本编辑。 + +import net.micode.notes.R; +// 导入R类,用于访问资源文件。 + +import java.util.HashMap; +// 导入HashMap类,用于存储键值对。 +import java.util.Map; +// 导入Map接口,用于映射。 + +// NoteEditText是继承自EditText的自定义控件,用于编辑便签内容 +public class NoteEditText extends EditText { + // 类变量,用于日志标记 + private static final String TAG = "NoteEditText"; + // 成员变量,记录便签索引 + private int mIndex; + // 成员变量,记录删除前的光标位置 + + // 定义不同的链接协议 + private static final String SCHEME_TEL = "tel:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + // 用于存放不同协议对应的操作资源ID + private static final Map sSchemaActionResMap = new HashMap(); + static { + // 初始化协议对应的资源ID映射 + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + /** + * OnTextViewChangeListener接口,用于监听文本编辑事件 + */ + public interface OnTextViewChangeListener { + /** + * 当按下删除键且文本为空时,删除当前编辑文本 + */ + void onEditTextDelete(int index, String text); + /** + * 当按下回车键时,在当前编辑文本后添加新的编辑文本 + */ + void onEditTextEnter(int index, String text); + /** + * 当文本变化时,隐藏或显示项目选项 + */ + void onTextChange(int index, boolean hasText); + } + + private OnTextViewChangeListener mOnTextViewChangeListener; // 文本编辑事件监听器 + + // NoteEditText构造函数 + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; + } + + // 设置便签索引 + public void setIndex(int index) { + mIndex = index; + } + + // 设置文本编辑事件监听器 + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + // NoteEditText构造函数,通过AttributeSet传递属性 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + // NoteEditText构造函数,通过AttributeSet和defStyle传递属性和样式 + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + // 处理触摸事件,用于设置光标位置 + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // 计算光标位置 + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event); + } + + // 处理按键事件,用于处理回车和删除键 + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + // 如果设置了文本编辑事件监听器,则不执行默认操作 + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + // 记录删除前的光标位置 + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + return super.onKeyDown(keyCode, event); + } + + // 处理按键释放事件,用于处理删除和回车键 + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_DEL: + // 如果设置了文本编辑事件监听器,且光标在开头,则删除当前便签 + if (mOnTextViewChangeListener != null) { + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + // 如果设置了文本编辑事件监听器,在当前文本后添加新的编辑文本 + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart(); + String text = getText().subSequence(selectionStart, length()).toString(); + setText(getText().subSequence(0, selectionStart)); + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event); + } + + // 处理焦点变化事件,用于更新项目选项的显示 + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener != null) { + // 根据是否有文本,隐藏或显示项目选项 + if (!focused && TextUtils.isEmpty(getText())) { + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true); + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + // 创建上下文菜单,用于处理文本的上下文操作 + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (getText() instanceof Spanned) { + // 获取选中的文本 + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd); + + // 获取URLSpan对象 + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + if (urls.length == 1) { + // 获取默认资源ID + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) { + if(urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { +defaultResId = R.string.note_link_other; +} + + // 添加菜单项 + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // 跳转到新的意图 + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); +} +} \ No newline at end of file diff --git a/src/ui(r)/NoteItemData.java b/src/ui(r)/NoteItemData.java new file mode 100644 index 0000000..09c4e0e --- /dev/null +++ b/src/ui(r)/NoteItemData.java @@ -0,0 +1,231 @@ +/* + * 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.database.Cursor; +import android.text.TextUtils; + +import net.micode.notes.data.Contact; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.DataUtils; + +// NoteItemData类用于封装便签项的数据 +public class NoteItemData { + // 数据库查询的列 + static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + }; + + // 列索引 + private static final int ID_COLUMN = 0; + private static final int ALERTED_DATE_COLUMN = 1; + private static final int BG_COLOR_ID_COLUMN = 2; + private static final int CREATED_DATE_COLUMN = 3; + private static final int HAS_ATTACHMENT_COLUMN = 4; + private static final int MODIFIED_DATE_COLUMN = 5; + private static final int NOTES_COUNT_COLUMN = 6; + private static final int PARENT_ID_COLUMN = 7; + private static final int SNIPPET_COLUMN = 8; + private static final int TYPE_COLUMN = 9; + private static final int WIDGET_ID_COLUMN = 10; + private static final int WIDGET_TYPE_COLUMN = 11; + + // 成员变量,存储便签项的数据 + private long mId; + private long mAlertDate; + private int mBgColorId; + private long mCreatedDate; + private boolean mHasAttachment; + private long mModifiedDate; + private int mNotesCount; + private long mParentId; + private String mSnippet; + private int mType; + private int mWidgetId; + private int mWidgetType; + private String mName; + private String mPhoneNumber; + + // 用于标记便签项的位置状态 + private boolean mIsLastItem; + private boolean mIsFirstItem; + private boolean mIsOnlyOneItem; + private boolean mIsOneNoteFollowingFolder; + private boolean mIsMultiNotesFollowingFolder; + + // 构造函数,从游标中初始化便签项的数据 + public NoteItemData(Context context, Cursor cursor) { + mId = cursor.getLong(ID_COLUMN); + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + mParentId = cursor.getLong(PARENT_ID_COLUMN); + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + + mPhoneNumber = ""; + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + if (!TextUtils.isEmpty(mPhoneNumber)) { + mName = Contact.getContact(context, mPhoneNumber); + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + if (mName == null) { + mName = ""; + } + checkPostion(cursor); + } + + // 检查便签项的位置状态 + private void checkPostion(Cursor cursor) { + mIsLastItem = cursor.isLast() ? true : false; + mIsFirstItem = cursor.isFirst() ? true : false; + mIsOnlyOneItem = (cursor.getCount() == 1); + mIsMultiNotesFollowingFolder = false; + mIsOneNoteFollowingFolder = false; + + if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { + int position = cursor.getPosition(); + if (cursor.moveToPrevious()) { + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true; + } else { + mIsOneNoteFollowingFolder = true; + } + } + if (!cursor.moveToNext()) { + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + // 获取便签项的位置状态 + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + public boolean isLast() { + return mIsLastItem; + } + + public String getCallName() { + return mName; + } + + public boolean isFirst() { + return mIsFirstItem; + } + + public boolean isSingle() { + return mIsOnlyOneItem; + } + + public long getId() { + return mId; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getCreatedDate() { + return mCreatedDate; + } + + public boolean hasAttachment() { + return mHasAttachment; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorId() { + return mBgColorId; + } + + public long getParentId() { + return mParentId; + } + + public int getNotesCount() { + return mNotesCount; + } + + public long getFolderId () { + return mParentId; + } + + public int getType() { + return mType; + } + + public int getWidgetType() { + return mWidgetType; + } + + public int getWidgetId() { + return mWidgetId; + } + + public String getSnippet() { + return mSnippet; + } + + public boolean hasAlert() { + return (mAlertDate > 0); + } + + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); + } + + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } +} \ No newline at end of file diff --git a/src/ui(r)/NotesListAdapter.java b/src/ui(r)/NotesListAdapter.java new file mode 100644 index 0000000..c2c3a1e --- /dev/null +++ b/src/ui(r)/NotesListAdapter.java @@ -0,0 +1,197 @@ +/* + * 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.database.Cursor; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; + +import net.micode.notes.data.Notes; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + +// NotesListAdapter是CursorAdapter的子类,用于为便签列表提供数据和视图 +public class NotesListAdapter extends CursorAdapter { + private static final String TAG = "NotesListAdapter"; + private Context mContext; // 上下文对象 + private HashMap mSelectedIndex; // 存储选中状态的哈希图 + private int mNotesCount; // 便签数量 + private boolean mChoiceMode; // 选择模式标志 + + // AppWidgetAttribute内部类,用于存储小部件的属性 + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + }; + + // 构造函数 + public NotesListAdapter(Context context) { + super(context, null); + mSelectedIndex = new HashMap(); + mContext = context; + mNotesCount = 0; + } + + // 新建视图 + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + + // 绑定视图和数据 + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) { + NoteItemData itemData = new NoteItemData(context, cursor); + ((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 getSelectedItemIds() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Long id = getItemId(position); + if (id == Notes.ID_ROOT_FOLDER) { + Log.d(TAG, "Wrong item id, should not happen"); + } else { + itemSet.add(id); + } + } + } + + return itemSet; + } + + // 获取选中的小部件属性集合 + public HashSet getSelectedWidget() { + HashSet itemSet = new HashSet(); + 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); + widget.widgetId = item.getWidgetId(); + widget.widgetType = item.getWidgetType(); + itemSet.add(widget); + } else { + Log.e(TAG, "Invalid cursor"); + return null; + } + } + } + return itemSet; + } + + // 获取选中数量 + public int getSelectedCount() { + Collection values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator 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); + } + + // 检查指定位置的项是否被选中 + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return 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; + } + } + } +} \ No newline at end of file diff --git a/src/ui(r)/NotesListItem.java b/src/ui(r)/NotesListItem.java new file mode 100644 index 0000000..bbd74f5 --- /dev/null +++ b/src/ui(r)/NotesListItem.java @@ -0,0 +1,131 @@ +/* + * 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; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser.NoteItemBgResources; + +// NotesListItem是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); // 初始化复选框 + } + + // bind方法用于绑定数据和视图 + 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) { + 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); + } + + // setBackground方法用于设置便签项的背景 + private void setBackground(NoteItemData data) { + int id = data.getBgColorId(); + 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()); + } + } + + // getItemData方法用于获取绑定的便签项数据 + public NoteItemData getItemData() { + return mItemData; + } +} \ No newline at end of file