20250511文件更新

wangyucheng_branch
WangYucheng 3 months ago
parent 90b9f1414c
commit 3d4e6fcc30

@ -4,6 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="corretto-18" /> <option name="gradleJvm" value="corretto-18" />
<option name="modules"> <option name="modules">

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="corretto-18" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_18_PREVIEW" project-jdk-name="corretto-18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

@ -36,7 +36,6 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0' implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.firebase:firebase-crashlytics-buildtools:2.8.1'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- AndroidManifest.xml 是 Android 应用的核心配置文件,定义了应用的基本信息、组件声明以及权限需求。-->
<!-- 定义应用所需的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--从 Android 13 开始WRITE_EXTERNAL_STORAGE 权限已被弃用
直接访问共享存储需要使用更安全的 API如 MediaStore以提升用户隐私保护。-->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
@ -16,17 +20,17 @@
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/icon_app"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Notesmaster" android:theme="@style/Theme.Notesmaster"
tools:targetApi="31"> tools:targetApi="31">
<!-- 定义主活动,作为应用的入口点 -->
<activity <activity
android:name=".ui.NotesListActivity" android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/NoteTheme" android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow" android:uiOptions="splitActionBarWhenNarrow"
@ -39,6 +43,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- 定义笔记编辑活动 -->
<activity <activity
android:name=".ui.NoteEditActivity" android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
@ -70,12 +75,17 @@
android:resource="@xml/searchable" /> android:resource="@xml/searchable" />
</activity> </activity>
<!-- 定义内容提供者 -->
<provider <provider
android:name="net.micode.notes.data.NotesProvider" android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes" android:authorities="micode_notes"
android:multiprocess="true" /> android:multiprocess="true"
android:exported="false"
android:readPermission="com.example.notesmaster.permission.READ_NOTES"
android:writePermission="com.example.notesmaster.permission.WRITE_NOTES" />
<!-- 定义小部件提供者 -->
<receiver <receiver
android:name=".widget.NoteWidgetProvider_2x" android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" android:label="@string/app_widget2x2"
@ -106,6 +116,7 @@
android:resource="@xml/widget_4x_info" /> android:resource="@xml/widget_4x_info" />
</receiver> </receiver>
<!-- 定义启动时初始化闹钟的接收器 -->
<receiver android:name=".ui.AlarmInitReceiver" <receiver android:name=".ui.AlarmInitReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -113,18 +124,20 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- 定义处理闹钟事件的接收器 -->
<receiver <receiver
android:name="net.micode.notes.ui.AlarmReceiver" android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" > android:process=":remote" >
</receiver> </receiver>
<!-- 定义闹钟提醒活动 -->
<activity <activity
android:name=".ui.AlarmAlertActivity" android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" > android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity> </activity>
<!-- 定义设置活动 -->
<activity <activity
android:name="net.micode.notes.ui.NotesPreferenceActivity" android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title" android:label="@string/preferences_title"
@ -132,11 +145,13 @@
android:theme="@android:style/Theme.Holo.Light" > android:theme="@android:style/Theme.Holo.Light" >
</activity> </activity>
<!-- 定义Google任务同步服务 -->
<service <service
android:name="net.micode.notes.gtask.remote.GTaskSyncService" android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" > android:exported="false" >
</service> </service>
<!-- 定义默认搜索活动 -->
<meta-data <meta-data
android:name="android.app.default_searchable" android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" /> android:value=".ui.NoteEditActivity" />
@ -149,7 +164,22 @@
<!-- <category android:name="android.intent.category.LAUNCHER" />--> <!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>--> <!-- </intent-filter>-->
<!-- </activity>--> <!-- </activity>
这段代码被注释化的原因可能有以下几种情况:
冗余配置:
如果应用已经有另一个活动(如 NotesListActivity作为主入口点并且已经正确配置了 <intent-filter>
那么再定义一个类似的 MainActivity 就是冗余的。为了避免冲突或重复配置,开发者选择将其注释掉。
开发阶段调整:
在开发过程中,开发者可能尝试过不同的入口点配置,最终选择了 NotesListActivity 作为主活动,
因此将 MainActivity 注释掉以保留历史代码供参考。
功能迁移:
如果应用的功能结构发生了变化,原本由 MainActivity 承担的功能现在由其他活动(如 NotesListActivity接管
那么 MainActivity 就不再需要作为主入口点。
测试或调试:
有时开发者会临时注释掉某些代码以进行测试或调试,之后忘记取消注释。-->
</application> </application>

@ -206,35 +206,35 @@ public class Notes {
/** /**
* Generic data column, the meaning is specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type * integer data type
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String DATA1 = "data1"; public static final String DATA1 = "data1";
/** /**
* Generic data column, the meaning is specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type * integer data type
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String DATA2 = "data2"; public static final String DATA2 = "data2";
/** /**
* Generic data column, the meaning is specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA3 = "data3"; public static final String DATA3 = "data3";
/** /**
* Generic data column, the meaning is specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA4 = "data4"; public static final String DATA4 = "data4";
/** /**
* Generic data column, the meaning is specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */

@ -24,20 +24,6 @@ import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.HttpEntity;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.HttpResponse;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.ClientProtocolException;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.entity.UrlEncodedFormEntity;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.methods.HttpGet;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.methods.HttpPost;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.conn.ClientConnectionManager;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.cookie.Cookie;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.impl.client.BasicCookieStore;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.impl.client.DefaultHttpClient;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.message.BasicNameValuePair;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.params.BasicHttpParams;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.params.HttpProtocolParams;
import net.micode.notes.gtask.data.Node; import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task; import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList; import net.micode.notes.gtask.data.TaskList;
@ -46,8 +32,20 @@ import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity; import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -230,10 +228,10 @@ public class GTaskClient {
private boolean loginGtask(String authToken) { private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; int timeoutConnection = 10000;
int timeoutSocket = 15000; int timeoutSocket = 15000;
HttpParams httpParameters = (HttpParams) new BasicHttpParams(); HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient((ClientConnectionManager) httpParameters); mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore); mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);

@ -430,7 +430,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) { if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE); mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE); View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) { } else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE); View.GONE);
@ -564,21 +564,36 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
/** /**
* Share note to apps that support {@link Intent#ACTION_SEND} action * {@link Intent#ACTION_SEND} {@text/plain}
* and {@text/plain} type *
* @param context Activity
* @param info {@link Intent#EXTRA_TEXT}
*/ */
private void sendTo(Context context, String info) { private void sendTo(Context context, String info) {
// 创建一个新的 Intent设置操作为 ACTION_SEND用于分享文本信息
Intent intent = new Intent(Intent.ACTION_SEND); Intent intent = new Intent(Intent.ACTION_SEND);
// 将要分享的文本信息作为 EXTRA_TEXT 添加到 Intent 中
intent.putExtra(Intent.EXTRA_TEXT, info); intent.putExtra(Intent.EXTRA_TEXT, info);
// 设置 Intent 的 MIME 类型为 text/plain表示分享的是纯文本
intent.setType("text/plain"); intent.setType("text/plain");
// 启动目标应用程序的 Activity分享文本信息
context.startActivity(intent); context.startActivity(intent);
} }
/**
*
* NoteEditActivity
* finish()ActivityIntentNoteEditActivity
*/
private void createNewNote() { private void createNewNote() {
// Firstly, save current editing notes // 首先保存当前正在编辑的笔记,确保数据不会丢失
saveNote(); saveNote();
// For safety, start a new NoteEditActivity // 结束当前Activity并启动一个新的NoteEditActivity以创建新笔记
finish(); finish();
Intent intent = new Intent(this, NoteEditActivity.class); Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
@ -586,25 +601,44 @@ public class NoteEditActivity extends Activity implements OnClickListener,
startActivity(intent); startActivity(intent);
} }
/**
*
*
*
* IDID
*
*
*/
private void deleteCurrentNote() { private void deleteCurrentNote() {
// 检查当前笔记是否存在于数据库中
if (mWorkingNote.existInDatabase()) { if (mWorkingNote.existInDatabase()) {
// 创建一个HashSet来存储待删除的笔记ID
HashSet<Long> ids = new HashSet<Long>(); HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId(); long id = mWorkingNote.getNoteId();
// 如果笔记ID不是根文件夹的ID则将其添加到待删除的ID集合中
if (id != Notes.ID_ROOT_FOLDER) { if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id); ids.add(id);
} else { } else {
// 如果笔记ID是根文件夹的ID则记录错误日志
Log.d(TAG, "Wrong note id, should not happen"); Log.d(TAG, "Wrong note id, should not happen");
} }
// 根据是否处于同步模式决定是直接删除还是移动到回收站
if (!isSyncMode()) { if (!isSyncMode()) {
// 如果不在同步模式,则尝试批量删除笔记
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error"); Log.e(TAG, "Delete Note error");
} }
} else { } else {
// 如果在同步模式,则尝试将笔记移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens"); Log.e(TAG, "Move notes to trash folder error, should not happens");
} }
} }
} }
// 无论笔记是否存在于数据库中,都将其标记为已删除状态
mWorkingNote.markDeleted(true); mWorkingNote.markDeleted(true);
} }
@ -612,14 +646,27 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
} }
/**
*
*
*
*
* @param date
* @param set true false
*/
public void onClockAlertChanged(long date, boolean set) { public void onClockAlertChanged(long date, boolean set) {
/** /**
* User could set clock to an unsaved note, so before setting the *
* alert clock, we should save the note first *
*/ */
if (!mWorkingNote.existInDatabase()) { if (!mWorkingNote.existInDatabase()) {
saveNote(); saveNote();
} }
/**
* ID0
* 使 AlarmManager PendingIntent AlarmReceiver
*/
if (mWorkingNote.getNoteId() > 0) { if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class); Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
@ -633,9 +680,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} else { } else {
/** /**
* There is the condition that user has input nothing (the note is *
* not worthy saving), we have no note id, remind the user that he *
* should input something
*/ */
Log.e(TAG, "Clock alert setting error"); Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock); showToast(R.string.error_note_empty_for_clock);
@ -646,18 +692,30 @@ public class NoteEditActivity extends Activity implements OnClickListener,
updateWidget(); updateWidget();
} }
/**
*
*
*
* @param index
* @param text
*/
public void onEditTextDelete(int index, String text) { public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount(); int childCount = mEditTextList.getChildCount();
// 如果只有一个编辑框,直接返回,不允许删除
if (childCount == 1) { if (childCount == 1) {
return; return;
} }
// 调整后续编辑框的索引
for (int i = index + 1; i < childCount; i++) { for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1); .setIndex(i - 1);
} }
// 移除指定索引的编辑框
mEditTextList.removeViewAt(index); mEditTextList.removeViewAt(index);
// 获取前一个或后一个编辑框,并将删除的文本内容追加到其中
NoteEditText edit = null; NoteEditText edit = null;
if(index == 0) { if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(

@ -82,136 +82,232 @@ public class NoteEditText extends EditText {
mIndex = 0; mIndex = 0;
} }
/**
*
*
*
* @param index
*/
public void setIndex(int index) { public void setIndex(int index) {
mIndex = index; mIndex = index;
} }
/**
*
*
*
* @param listener OnTextViewChangeListener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener; mOnTextViewChangeListener = listener;
} }
/**
* XML NoteEditText
*
* @param context
* @param attrs XML
*/
public NoteEditText(Context context, AttributeSet attrs) { public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle); super(context, attrs, android.R.attr.editTextStyle);
} }
/**
* XML NoteEditText
*
* @param context
* @param attrs XML
* @param defStyle ID
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) { public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub // TODO Auto-generated constructor stub
} }
/**
*
*
*
*
* @param event
* @return truefalse
*/
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
// 根据触摸事件的类型进行不同的处理
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// 获取触摸点相对于视图的坐标
int x = (int) event.getX(); int x = (int) event.getX();
int y = (int) event.getY(); int y = (int) event.getY();
// 调整坐标以考虑视图的内边距
x -= getTotalPaddingLeft(); x -= getTotalPaddingLeft();
y -= getTotalPaddingTop(); y -= getTotalPaddingTop();
// 调整坐标以考虑视图的滚动偏移
x += getScrollX(); x += getScrollX();
y += getScrollY(); y += getScrollY();
// 获取视图中的文本布局
Layout layout = getLayout(); Layout layout = getLayout();
// 根据垂直位置计算触摸点所在的文本行
int line = layout.getLineForVertical(y); int line = layout.getLineForVertical(y);
// 根据水平位置计算触摸点所在的文本偏移量
int off = layout.getOffsetForHorizontal(line, x); int off = layout.getOffsetForHorizontal(line, x);
// 更新文本选择
Selection.setSelection(getText(), off); Selection.setSelection(getText(), off);
break; break;
} }
// 调用父类的onTouchEvent方法以处理其他触摸事件
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
/**
* onKeyDown
*
* @param keyCode
* @param event
* @return truefalse
*/
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
// 根据不同的键码做相应的处理
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_ENTER:
// 当检测到ENTER键时如果mOnTextViewChangeListener不为空则不处理事件交由其他处理程序处理
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
return false; return false;
} }
break; break;
case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_DEL:
// 当检测到DEL键时记录删除操作前的光标位置
mSelectionStartBeforeDelete = getSelectionStart(); mSelectionStartBeforeDelete = getSelectionStart();
break; break;
default: default:
// 对于其他键码,不做特殊处理
break; break;
} }
// 对于未特殊处理的键码,调用父类的方法处理
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
/**
*
*
* @param keyCode
* @param event
* @return truefalse
*/
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
// 根据按键代码处理不同的按键事件
switch(keyCode) { switch(keyCode) {
case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_DEL:
// 处理删除键释放事件
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
// 如果删除前光标位置为0且不是第一个文本框则通知监听器进行删除操作
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true; return true;
} }
} else { } else {
// 如果监听器未设置,则记录日志
Log.d(TAG, "OnTextViewChangeListener was not seted"); Log.d(TAG, "OnTextViewChangeListener was not seted");
} }
break; break;
case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_ENTER:
// 处理回车键释放事件
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
// 获取当前光标位置并分离出光标后的文本,然后设置文本框内容为光标前的文本
int selectionStart = getSelectionStart(); int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString(); String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart)); setText(getText().subSequence(0, selectionStart));
// 通知监听器进行回车操作处理
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else { } else {
// 如果监听器未设置,则记录日志
Log.d(TAG, "OnTextViewChangeListener was not seted"); Log.d(TAG, "OnTextViewChangeListener was not seted");
} }
break; break;
default: default:
// 对于其他按键事件,不做任何处理
break; break;
} }
// 如果上述条件都不满足则调用父类的onKeyUp方法处理事件
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
/**
*
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// 如果有注册的文本视图变更监听器
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
// 如果视图失去焦点且文本为空则调用监听器的onTextChange方法参数为false
if (!focused && TextUtils.isEmpty(getText())) { if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false); mOnTextViewChangeListener.onTextChange(mIndex, false);
} else { } else {
// 否则调用监听器的onTextChange方法参数为true
mOnTextViewChangeListener.onTextChange(mIndex, true); mOnTextViewChangeListener.onTextChange(mIndex, true);
} }
} }
// 调用父类的onFocusChanged方法确保焦点改变的默认行为得以执行
super.onFocusChanged(focused, direction, previouslyFocusedRect); super.onFocusChanged(focused, direction, previouslyFocusedRect);
} }
/**
*
* URL
* URL
*/
@Override @Override
protected void onCreateContextMenu(ContextMenu menu) { protected void onCreateContextMenu(ContextMenu menu) {
// 检查当前文本是否为Spanned类型以支持富文本
if (getText() instanceof Spanned) { if (getText() instanceof Spanned) {
// 获取选中文本的起始和结束位置
int selStart = getSelectionStart(); int selStart = getSelectionStart();
int selEnd = getSelectionEnd(); int selEnd = getSelectionEnd();
// 确定选中文本的最小和最大位置,以处理选中区域
int min = Math.min(selStart, selEnd); int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd); int max = Math.max(selStart, selEnd);
// 获取选中文本范围内的所有URLSpan对象
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
// 如果选中的文本中仅有一个URL则进一步处理
if (urls.length == 1) { if (urls.length == 1) {
int defaultResId = 0; int defaultResId = 0;
// 遍历预定义的schema-action资源映射查找匹配的URL schema
for(String schema: sSchemaActionResMap.keySet()) { for(String schema: sSchemaActionResMap.keySet()) {
// 如果URL包含当前schema则获取对应的资源ID并停止遍历
if(urls[0].getURL().indexOf(schema) >= 0) { if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema); defaultResId = sSchemaActionResMap.get(schema);
break; break;
} }
} }
// 如果没有找到匹配的schema则使用默认的“其他”资源
if (defaultResId == 0) { if (defaultResId == 0) {
defaultResId = R.string.note_link_other; defaultResId = R.string.note_link_other;
} }
// 在上下文菜单中添加一个菜单项,并设置点击事件处理
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() { new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
// goto a new intent // 点击菜单项时触发URL的onClick事件通常会启动一个新的Activity
urls[0].onClick(NoteEditText.this); urls[0].onClick(NoteEditText.this);
return true; return true;
} }
}); });
} }
} }
// 调用父类方法,确保菜单被正确创建
super.onCreateContextMenu(menu); super.onCreateContextMenu(menu);
} }
} }

@ -76,39 +76,76 @@ public class NoteItemData {
private boolean mIsOneNoteFollowingFolder; private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder; private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) { /**
mId = cursor.getLong(ID_COLUMN); * NoteItemData Cursor NoteItemData
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); *
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); * @param context
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); * @param cursor Cursor
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; */
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); public NoteItemData(Context context, Cursor cursor) {
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); // 从 Cursor 中提取笔记的 ID
mParentId = cursor.getLong(PARENT_ID_COLUMN); mId = cursor.getLong(ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( // 从 Cursor 中提取笔记的提醒日期
NoteEditActivity.TAG_UNCHECKED, ""); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); // 从 Cursor 中提取笔记的背景颜色 ID
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mPhoneNumber = ""; // 从 Cursor 中提取笔记的创建日期
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) { // 从 Cursor 中提取笔记是否包含附件的信息,并转换为布尔值
mName = Contact.getContact(context, mPhoneNumber); mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
if (mName == null) {
mName = mPhoneNumber; // 从 Cursor 中提取笔记的最后修改日期
} mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
// 从 Cursor 中提取笔记下的子笔记数量
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
// 从 Cursor 中提取笔记的父级 ID即所属文件夹的 ID
mParentId = cursor.getLong(PARENT_ID_COLUMN);
// 从 Cursor 中提取笔记的摘要信息并移除标记符号TAG_CHECKED 和 TAG_UNCHECKED
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(NoteEditActivity.TAG_UNCHECKED, "");
// 从 Cursor 中提取笔记的类型(如普通笔记、文件夹等)
mType = cursor.getInt(TYPE_COLUMN);
// 从 Cursor 中提取笔记的小部件 ID
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 从 Cursor 中提取笔记的小部件类型
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 仍为 null则将其设置为空字符串
mName = ""; if (mName == null) {
} mName = "";
checkPostion(cursor);
} }
// 检查笔记在 Cursor 中的位置信息(如是否为第一个、最后一个等)
checkPostion(cursor);
}
private void checkPostion(Cursor cursor) { private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false; mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false;
@ -158,61 +195,131 @@ public class NoteItemData {
return mIsOnlyOneItem; return mIsOnlyOneItem;
} }
public long getId() { /**
return mId; *
} *
* @return ID
*/
public long getId() {
return mId;
}
public long getAlertDate() { /**
return mAlertDate; *
} *
* @return 0
*/
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() { /**
return mCreatedDate; *
} *
* @return
*/
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() { /**
return mHasAttachment; *
} *
* @return true false
*/
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() { /**
return mModifiedDate; *
} *
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() { /**
return mBgColorId; * ID
} *
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() { /**
return mParentId; * ID ID
} *
* @return ID
*/
public long getParentId() {
return mParentId;
}
public int getNotesCount() { /**
return mNotesCount; *
} *
* @return
*/
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () { /**
return mParentId; * ID getParentId()
} *
* @return ID
*/
public long getFolderId() {
return mParentId;
}
public int getType() { /**
return mType; *
} *
* @return
*/
public int getType() {
return mType;
}
public int getWidgetType() { /**
return mWidgetType; *
} *
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() { /**
return mWidgetId; * ID
} *
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() { /**
return mSnippet; *
} *
* @return
*/
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() { /**
return (mAlertDate > 0); *
} *
* @return true false
*/
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() { public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));

@ -63,7 +63,6 @@
</style> </style>
<style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid"> <style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<item name="android:displayOptions" /> <item name="android:visibility">visible</item>
<item name="android:visibility">gone</item>
</style> </style>
</resources> </resources>

@ -1,2 +1,42 @@
# Notes_master # 小米便签项目分析与维护
## 📖 项目背景
中国民航大学计算机科学与技术学院软件工程课程小组作业针对小米便签MiNotes开源项目进行深度源码分析、架构研究和维护优化实践。项目成员董雯喆、郭佳豪、万文广、王禹程、王飞按首字母排列排名不分先后
---
## 🧩 核心架构分析
### 分层架构模式
1. **数据层Model**
- SQLite数据库管理笔记元数据
- ContentProvider实现数据共享
- FileSystemManager处理附件存储
2. **业务逻辑层Presenter**
- NoteOperator核心业务处理器
- 异步任务管理AsyncTask线程池
- 数据变更通知机制Observer模式
3. **表现层View**
- RecyclerView实现笔记列表
- RichEditor自定义富文本编辑器
- 多主题适配框架
---
## ⚙️启动方式
1. 连接手机打开USB调试或启动虚拟机
2. 在Android Studio或Intellij IDEA中打开项目
3. 若成功识别到设备即可运行项目
4. 同意在手机或虚拟机上安装APK安装完成即可成功启动项目
---
## 🔧 维护与优化方案
### 现存问题
1. 富文本编辑器性能瓶颈
2. 所用数据包过于老旧
## 📄 许可证
遵循原始项目Apache License 2.0协议详见LICENSE文件
Loading…
Cancel
Save