Compare commits

..

78 Commits

Author SHA1 Message Date
pg6wepubk 6d1cbd560e Merge pull request '完善' (#63) from mengcheng_branch into develop
2 months ago
mc19 a560e67b48 完善
2 months ago
pg6wepubk 82d5d7ef3c Merge pull request '规范代码质量分析报告' (#61) from mengcheng_branch into develop
2 months ago
mc19 e0ff6c3791 规范代码质量分析报告
2 months ago
pg6wepubk 99b77eb629 Merge pull request '完成代码质量分析报告,修改泛读报告' (#59) from mengcheng_branch into develop
2 months ago
mc19 59fc87327b 完成质量分析报告,修改泛读报告
2 months ago
pg6wepubk 6deaa7fc13 Merge pull request '完成代码质量分析报告' (#57) from mengcheng_branch into develop
2 months ago
mc19 2fdc35164e 完成代码质量分析报告
2 months ago
pg6wepubk 16692d629a Merge pull request '撰写质量分析报告' (#56) from mengcheng_branch into develop
2 months ago
mc19 6a0a657817 撰写质量分析报告文档
2 months ago
mc19 36a9259bc9 增加分析文件
2 months ago
mc19 6bf950ffe7 补充文档注释部分
2 months ago
pg6wepubk 75559147eb Merge pull request '增加前三个包注释' (#54) from mengcheng_branch into develop
2 months ago
mc19 2e41cabb83 删除原文档
2 months ago
mc19 552d11bbbb Merge branch 'develop' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading into mengcheng_branch
2 months ago
pkx2w7qjn 51a5d26bca Merge pull request '完善文档标注部分' (#52) from zhaochang_branch into develop
2 months ago
pkx2w7qjn b2251059cf Merge pull request '添加tool包,ui包,widget包注释' (#50) from zhaochang_branch into develop
2 months ago
赵昌 1e13ff446b 重置代码
2 months ago
赵昌 b55b47c8dc 添加tool包,UI包,widget包注释
2 months ago
mc19 bc3beabee4 完善报告代码行数
2 months ago
mc19 cc040dfac9 增加data,gtask,model包注释
2 months ago
mc19 be3561be23 修改格式
3 months ago
mc19 759789a3a3 修改文档名
3 months ago
mc19 b44085160c 删除格式错误文档
3 months ago
mc19 b39b458497 修改一些格式问题
3 months ago
mc19 bea04f9471 修改格式
3 months ago
mc19 e1060a9d80 补充最新模板
3 months ago
赵昌 f0253694e9 Merge branch 'master' of https://bdgit.educoder.net/pkx2w7qjn/xiaomi_notes_reading
3 months ago
pkx2w7qjn c4e56fcb20 Merge pull request '修改部分内容' (#38) from develop into master
3 months ago
赵昌 d99b0e6b4e 修改小米便签泛读、标注和维护报告文档
3 months ago
pkx2w7qjn 93618c9d45 Merge pull request '添加小米便签泛读、标注和维护报告文档' (#36) from develop into master
3 months ago
pkx2w7qjn f27d797a50 Merge pull request '添加小米便签泛读、标注和维护报告文档' (#35) from zhaochang_branch into develop
3 months ago
赵昌 201d9abcfb 修改部分小米便签泛读,标注和维护报告文档内容
3 months ago
赵昌 bc9d3780ad Merge branch 'master' of https://bdgit.educoder.net/pkx2w7qjn/xiaomi_notes_reading
3 months ago
赵昌 fdb3c2ae18 修改ui包类间关系图
3 months ago
mc19 587b774777 报告最终版
3 months ago
mc19 9b1d354da3 第三周修改报告
3 months ago
mc19 0b354512d1 修改格式
3 months ago
mc19 e49cc3e4f1 修改图片
3 months ago
pg6wepubk b5f6698ea0 Merge pull request '完成第三部分,同时修改完善其他部分' (#20) from develop into master
3 months ago
pg6wepubk 8ed90ccbf8 Merge pull request '完成第三部分,同时修改完善其他部分' (#19) from mengcheng_branch into develop
3 months ago
mc19 62599eb986 Merge branch 'master' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading
3 months ago
mc19 7ec2d9ca86 完善泛读报告
3 months ago
pg6wepubk 79cd82e231 Merge pull request '完善泛读报告' (#18) from mengcheng_branch into master
3 months ago
mc19 d1d1d1b239 完善泛读报告
3 months ago
pkx2w7qjn f8f0baaa2d Merge pull request '完成报告第四部分' (#17) from develop into master
3 months ago
pkx2w7qjn 997154d3ce Merge pull request '完成报告第四部分' (#16) from zhaochang_branch into develop
3 months ago
pkx2w7qjn adcb1950ce Merge pull request '修改部分内容' (#15) from develop into master
3 months ago
pkx2w7qjn 24e0a5b336 Merge pull request '修改部分内容' (#14) from zhaochang_branch into develop
3 months ago
pkx2w7qjn a8502fb77e Merge pull request '完成小米便签阅读报告第二部分' (#13) from develop into master
3 months ago
pkx2w7qjn d290615d0b Merge pull request '完成小米便签阅读报告第二部分' (#12) from zhaochang_branch into develop
3 months ago
赵昌 75ed1223e1 Merge branch 'develop' of https://bdgit.educoder.net/pkx2w7qjn/xiaomi_notes_reading into develop
3 months ago
赵昌 fb984eb6de 完成小米便签阅读报告第二部分
3 months ago
赵昌 8ad11877e6 完成小米便签阅读第二部分
3 months ago
赵昌 cde409e3bd Merge branch 'master' of https://bdgit.educoder.net/pkx2w7qjn/xiaomi_notes_reading
3 months ago
赵昌 a4523b267c 修改第一周报告
3 months ago
pkx2w7qjn f8e6b434c6 Merge pull request '修改分支文件' (#6) from mengcheng_branch into develop
3 months ago
赵昌 186d6c4474 修改第一周报告
3 months ago
mc19 5dffb53534 Merge branch 'develop' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading into develop
3 months ago
mc19 4cb65cc568 删除多余文件,修改文件名
3 months ago
mc19 e0f404109b 删除文件,修改文件名
3 months ago
mc19 df52bb9e51 删除多余文件
3 months ago
pg6wepubk 8b0a5aca63 Merge pull request '修改分支doc文件夹内容' (#5) from mengcheng_branch into master
3 months ago
mc19 a336c7e0fa Merge branch 'mengcheng_branch' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading into mengcheng_branch
3 months ago
mc19 8122ebf446 修改报告
3 months ago
pkx2w7qjn 37ae398e69 Merge pull request '提交第一周泛读报告' (#4) from develop into master
3 months ago
pkx2w7qjn e27bfa76b2 Merge pull request '提交第一周泛读报告' (#3) from mengcheng_branch into develop
3 months ago
pkx2w7qjn 1b2175faf7 Merge pull request '提交第一周泛读报告' (#2) from develop into master
3 months ago
pkx2w7qjn 6720f27cce Merge pull request '提交第一周泛读报告' (#1) from zhaochang_branch into develop
3 months ago
mc19 1c464945db 修改报告
3 months ago
mc19 b1f7eb82ac 修改报告
3 months ago
mc19 f2ae1799b2 Merge branch 'mengcheng_branch' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading into mengcheng_branch
3 months ago
mc19 33c39a526e 新增泛读报告
3 months ago
mc19 939ed40d4f 新增字母
3 months ago
mc19 48a09df8cc Merge branch 'mengcheng_branch' of https://bdgit.educoder.net/pg6wepubk/xiaomi_notes_reading into mengcheng_branch
3 months ago
mc19 e2fc1603e1 新增doc/hello.txt文件
3 months ago
mc19 69b513a2f4 空行删除
3 months ago
mc19 18886ebfde 新增数字
3 months ago

@ -195,60 +195,12 @@ public class Notes {
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* Alert location latitude
* <P> Type: REAL </P>
*/
public static final String ALERT_LATITUDE = "alert_latitude";
/**
* Alert location longitude
* <P> Type: REAL </P>
*/
public static final String ALERT_LONGITUDE = "alert_longitude";
/**
* Alert location radius
* <P> Type: REAL </P>
*/
public static final String ALERT_RADIUS = "alert_radius";
/**
* Alert location name
* <P> Type: TEXT </P>
*/
public static final String ALERT_LOCATION_NAME = "alert_location_name";
/**
* Note tag
* <P> Type: TEXT </P>
*/
public static final String TAG = "tag";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
/**
* Lock status: 0-unlocked, 1-locked
* <P> Type: INTEGER </P>
*/
public static final String LOCKED = "locked";
/**
* Lock type: 0-none, 1-password, 2-gesture
* <P> Type: INTEGER </P>
*/
public static final String LOCK_TYPE = "lock_type";
/**
* Encrypted password for privacy lock
* <P> Type: TEXT </P>
*/
public static final String ENCRYPTED_PASSWORD = "encrypted_password";
}

@ -54,7 +54,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
*/
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 8;
private static final int DB_VERSION = 4;
/**表明常量接口*/
public interface TABLE {
@ -94,15 +94,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TAG + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERT_LATITUDE + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_LONGITUDE + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_RADIUS + " REAL NOT NULL DEFAULT 0," +
NoteColumns.ALERT_LOCATION_NAME + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.LOCKED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ENCRYPTED_PASSWORD + " TEXT NOT NULL DEFAULT ''" +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
/**
@ -409,26 +401,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
if (oldVersion == 4) {
upgradeToV5(db);
oldVersion++;
}
if (oldVersion == 5) {
upgradeToV6(db);
oldVersion++;
}
if (oldVersion == 6) {
upgradeToV7(db);
oldVersion++;
}
if (oldVersion == 7) {
upgradeToV8(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@ -436,7 +408,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ " fails");
+ "fails");
}
}
@ -469,41 +441,4 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
//v4到v5的升级为note表新增tag列且约束非空默认为空字符串
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TAG
+ " TEXT NOT NULL DEFAULT ''");
}
//v5到v6的升级为note表新增位置提醒相关的列
private void upgradeToV6(SQLiteDatabase db) {
// 添加位置提醒相关的列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LATITUDE
+ " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LONGITUDE
+ " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_RADIUS
+ " REAL NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LOCATION_NAME
+ " TEXT NOT NULL DEFAULT ''");
}
//v6到v7的升级
private void upgradeToV7(SQLiteDatabase db) {
// 移除重复添加的位置提醒列
// 这些列已经在 v6 版本中添加过了
}
//v7到v8的升级为 note表新增隐私锁相关的列
private void upgradeToV8(SQLiteDatabase db) {
// 锁定状态0-未锁定1-已锁定
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCKED + " INTEGER NOT NULL DEFAULT 0");
// 锁类型0-无锁1-密码锁2-手势锁
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0");
// 加密后的密码
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ENCRYPTED_PASSWORD + " TEXT NOT NULL DEFAULT ''");
}
}

@ -1,155 +0,0 @@
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
*
*/
public class UserDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
private static final int DB_VERSION = 1;
private static final String TAG = "UserDatabaseHelper";
public interface TABLE {
public static final String USER = "user";
}
/**
*
*/
public interface UserColumns {
public static final String ID = "_id";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String CREATED_DATE = "created_date";
}
private static final String CREATE_USER_TABLE_SQL =
"CREATE TABLE " + TABLE.USER + "(" +
UserColumns.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
UserColumns.USERNAME + " TEXT UNIQUE NOT NULL," +
UserColumns.PASSWORD + " TEXT NOT NULL," +
UserColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)" +
")";
private static UserDatabaseHelper mInstance;
public UserDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static synchronized UserDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new UserDatabaseHelper(context.getApplicationContext());
}
return mInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_USER_TABLE_SQL);
Log.d(TAG, "user table has been created");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 目前只有一个版本,暂时不做升级处理
if (oldVersion < newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.USER);
onCreate(db);
}
}
/**
*
* @param username
* @param password
* @return truefalse
*/
public boolean registerUser(String username, String password) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(UserColumns.USERNAME, username);
values.put(UserColumns.PASSWORD, encryptPassword(password));
try {
long result = db.insert(TABLE.USER, null, values);
return result != -1;
} catch (Exception e) {
Log.e(TAG, "Register user error: " + e.getMessage());
return false;
}
}
/**
*
* @param username
* @param password
* @return truefalse
*/
public boolean authenticateUser(String username, String password) {
SQLiteDatabase db = this.getReadableDatabase();
String encryptedPassword = encryptPassword(password);
String[] columns = {UserColumns.ID};
String selection = UserColumns.USERNAME + "=? AND " + UserColumns.PASSWORD + "=?";
String[] selectionArgs = {username, encryptedPassword};
Cursor cursor = db.query(TABLE.USER, columns, selection, selectionArgs, null, null, null);
boolean authenticated = cursor.getCount() > 0;
cursor.close();
return authenticated;
}
/**
*
* @param username
* @return truefalse
*/
public boolean isUsernameExists(String username) {
SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {UserColumns.ID};
String selection = UserColumns.USERNAME + "=?";
String[] selectionArgs = {username};
Cursor cursor = db.query(TABLE.USER, columns, selection, selectionArgs, null, null, null);
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}
/**
* MD5
* @param password
* @return
*/
private String encryptPassword(String password) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(password.getBytes());
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "MD5 algorithm not found: " + e.getMessage());
return password; // 如果加密失败,返回原密码(实际项目中应该更安全地处理)
}
}
}

@ -81,7 +81,7 @@ public class GTaskSyncService extends Service {
/**
*
* /
* @param intent The Intent supplied to {@link Context#startService},
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.

@ -46,17 +46,11 @@ public class WorkingNote {
private int mMode;
private long mAlertDate;
private double mAlertLatitude;
private double mAlertLongitude;
private float mAlertRadius;
private String mAlertLocationName;
private long mModifiedDate;
private int mBgColorId;
private String mTag;
private int mWidgetId;
private int mWidgetType;
@ -89,12 +83,7 @@ public class WorkingNote {
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE,
NoteColumns.ALERT_LATITUDE,
NoteColumns.ALERT_LONGITUDE,
NoteColumns.ALERT_RADIUS,
NoteColumns.ALERT_LOCATION_NAME,
NoteColumns.TAG
NoteColumns.MODIFIED_DATE
};
private static final int DATA_ID_COLUMN = 0;
@ -116,20 +105,11 @@ public class WorkingNote {
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
private static final int NOTE_ALERT_LATITUDE_COLUMN = 6;
private static final int NOTE_ALERT_LONGITUDE_COLUMN = 7;
private static final int NOTE_ALERT_RADIUS_COLUMN = 8;
private static final int NOTE_ALERT_LOCATION_NAME_COLUMN = 9;
private static final int NOTE_TAG_COLUMN = 10;
// New note construct
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
mAlertLatitude = 0;
mAlertLongitude = 0;
mAlertRadius = 0;
mAlertLocationName = "";
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note();
@ -167,11 +147,6 @@ public class WorkingNote {
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
mAlertLatitude = cursor.getDouble(NOTE_ALERT_LATITUDE_COLUMN);
mAlertLongitude = cursor.getDouble(NOTE_ALERT_LONGITUDE_COLUMN);
mAlertRadius = cursor.getFloat(NOTE_ALERT_RADIUS_COLUMN);
mAlertLocationName = cursor.getString(NOTE_ALERT_LOCATION_NAME_COLUMN);
mTag = cursor.getString(NOTE_TAG_COLUMN);
}
cursor.close();
} else {
@ -340,61 +315,6 @@ public class WorkingNote {
}
}
//获取笔记标题(第一行)
public String getTitle() {
if (TextUtils.isEmpty(mContent)) {
return "";
}
int firstLineEnd = mContent.indexOf('\n');
if (firstLineEnd == -1) {
// 如果只有一行,全部作为标题
return mContent;
}
return mContent.substring(0, firstLineEnd);
}
//获取去掉标题后的正文内容
public String getContentWithoutTitle() {
if (TextUtils.isEmpty(mContent)) {
return "";
}
int firstLineEnd = mContent.indexOf('\n');
if (firstLineEnd == -1) {
// 如果只有一行,正文为空
return "";
}
return mContent.substring(firstLineEnd + 1);
}
//智能推荐标题:根据正文内容生成推荐标题
public String generateSmartTitle(String content) {
if (TextUtils.isEmpty(content)) {
return "";
}
// 去掉换行符取前50个字符作为推荐标题
String cleanContent = content.replace('\n', ' ').trim();
if (cleanContent.length() <= 50) {
return cleanContent;
}
return cleanContent.substring(0, 50) + "...";
}
//设置标题和正文
public void setTitleAndContent(String title, String content) {
String fullContent;
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(content)) {
fullContent = "";
} else if (TextUtils.isEmpty(content)) {
fullContent = title;
} else if (TextUtils.isEmpty(title)) {
fullContent = content;
} else {
fullContent = title + "\n" + content;
}
setWorkingText(fullContent);
}
//设置笔记内容
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
@ -413,46 +333,6 @@ public class WorkingNote {
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
public boolean hasLocationAlert() {
return (mAlertLatitude != 0 && mAlertLongitude != 0);
}
public void setAlertLocation(double latitude, double longitude, float radius, String locationName, boolean set) {
// 有位置变化才更新
if (latitude != mAlertLatitude || longitude != mAlertLongitude || radius != mAlertRadius || !locationName.equals(mAlertLocationName)) {
mAlertLatitude = latitude;
mAlertLongitude = longitude;
mAlertRadius = radius;
mAlertLocationName = locationName;
mNote.setNoteValue(NoteColumns.ALERT_LATITUDE, String.valueOf(mAlertLatitude));
mNote.setNoteValue(NoteColumns.ALERT_LONGITUDE, String.valueOf(mAlertLongitude));
mNote.setNoteValue(NoteColumns.ALERT_RADIUS, String.valueOf(mAlertRadius));
mNote.setNoteValue(NoteColumns.ALERT_LOCATION_NAME, mAlertLocationName);
}
// 若监听器不为空,触发位置提醒变更回调
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onLocationAlertChanged(latitude, longitude, radius, locationName, set);
}
}
public double getAlertLatitude() {
return mAlertLatitude;
}
public double getAlertLongitude() {
return mAlertLongitude;
}
public float getAlertRadius() {
return mAlertRadius;
}
public String getAlertLocationName() {
return mAlertLocationName;
}
public String getContent() {
return mContent;
@ -498,15 +378,6 @@ public class WorkingNote {
return mWidgetType;
}
public String getTag() {
return mTag;
}
public void setTag(String tag) {
mTag = tag;
mNote.setNoteValue(NoteColumns.TAG, tag);
}
// 笔记设置变更监听器接口(定义属性变化时的回调方法)
public interface NoteSettingChangedListener {
/**
@ -518,11 +389,6 @@ public class WorkingNote {
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Called when user set location alert
*/
void onLocationAlertChanged(double latitude, double longitude, float radius, String locationName, boolean set);
/**
* Call when user create note from widget

@ -1,624 +0,0 @@
package net.micode.notes.tool;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* AI
* 使AI API
*/
public class AIService {
private Context mContext;
private OnAITitleGeneratedListener mListener;
public interface OnAITitleGeneratedListener {
void onTitleGenerated(String title);
}
public AIService(Context context) {
mContext = context;
}
public void setOnAITitleGeneratedListener(OnAITitleGeneratedListener listener) {
mListener = listener;
}
/**
*
* @param content
*/
public void generateSmartTitle(String content) {
if (TextUtils.isEmpty(content)) {
if (mListener != null) {
mListener.onTitleGenerated("");
}
return;
}
// 使用异步任务模拟AI调用
new GenerateTitleTask().execute(content);
}
/**
* AI
*/
private class GenerateTitleTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String content = params[0];
// 模拟AI处理延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟AI生成标题逻辑
// 这里可以替换为真实的AI API调用如GPT、百度文心一言等
return generateMockAITitle(content);
}
@Override
protected void onPostExecute(String title) {
if (mListener != null) {
mListener.onTitleGenerated(title);
}
}
}
/**
* AI
* @param content
* @return
*/
private String generateMockAITitle(String content) {
// 清理内容,去掉特殊字符
String cleanContent = content.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5 \\t\\n\\r\\f\\v]", "").trim();
// 分析内容主题
String theme = analyzeTheme(cleanContent);
// 分析内容类型
String contentType = analyzeContentType(cleanContent);
// 提取核心关键词
List<String> coreKeywords = extractCoreKeywords(cleanContent, theme);
// 根据主题、类型和核心关键词生成标题
return generateTitleByThemeTypeAndKeywords(cleanContent, theme, contentType, coreKeywords);
}
/**
*
* @param content
* @return
*/
private String analyzeTheme(String content) {
// 简洁的主题关键词库,只保留通用主题词汇,去掉重复和具体课程名称
String[] themes = {
"学习", "工作", "会议", "计划", "总结", "任务", "想法",
"灵感", "生活", "旅行", "美食", "健康", "运动", "训练",
"购物", "阅读", "写作", "思考", "讨论", "研究", "开发", "设计",
"项目", "方案", "报告", "文档", "邮件", "电话", "客户",
"产品", "市场", "销售", "运营", "财务", "人事", "管理",
"技术", "代码", "测试", "上线", "维护", "优化", "创新",
"家庭", "朋友", "聚会", "电影", "音乐", "游戏", "动漫",
"书籍", "文章", "新闻", "资讯", "医疗", "养生",
"烹饪", "烘焙", "餐厅", "外卖", "食材", "食谱",
"景点", "酒店", "交通", "攻略", "行程", "机票",
"商品", "价格", "优惠", "促销", "订单", "物流",
"健身", "锻炼", "瑜伽", "跑步", "游泳", "篮球", "足球"
};
// 主题相关词汇映射,为每个主题添加丰富的相关词汇
Map<String, List<String>> themeRelatedWords = new HashMap<>();
// 学习相关词汇
themeRelatedWords.put("学习", Arrays.asList("学习", "课程", "考试", "作业", "论文", "复习", "预习",
"毛概", "系统工程", "数据结构", "数学", "英语", "物理", "化学",
"语文", "历史", "地理", "政治", "计算机", "编程", "算法"));
// 工作相关词汇
themeRelatedWords.put("工作", Arrays.asList("工作", "会议", "报告", "项目", "计划", "总结", "方案",
"文档", "邮件", "电话", "客户", "产品", "市场", "销售",
"运营", "财务", "人事", "管理", "技术", "代码", "测试",
"上线", "维护", "优化", "创新"));
// 会议相关词汇
themeRelatedWords.put("会议", Arrays.asList("会议", "讨论", "议题", "议程", "记录", "结论", "决策",
"参会", "主持", "发言", "汇报", "演示", "PPT", "视频会议"));
// 计划相关词汇
themeRelatedWords.put("计划", Arrays.asList("计划", "安排", "日程", "时间", "任务", "目标", "步骤",
"进度", "时间表", "规划", "策划", "方案", "预算", "资源"));
// 总结相关词汇
themeRelatedWords.put("总结", Arrays.asList("总结", "回顾", "反思", "收获", "体会", "感悟", "经验",
"教训", "成果", "不足", "改进", "建议", "报告", "汇报"));
// 任务相关词汇
themeRelatedWords.put("任务", Arrays.asList("任务", "工作", "项目", "目标", "责任", "分工", "协作",
"完成", "交付", "验收", "评估", "绩效", "效率", "质量"));
// 生活相关词汇
themeRelatedWords.put("生活", Arrays.asList("生活", "家庭", "朋友", "聚会", "电影", "音乐", "游戏",
"动漫", "书籍", "文章", "新闻", "资讯", "健康", "医疗",
"养生", "烹饪", "烘焙", "美食", "餐厅", "外卖", "食材"));
// 旅行相关词汇
themeRelatedWords.put("旅行", Arrays.asList("旅行", "景点", "酒店", "交通", "攻略", "行程", "机票",
"车票", "住宿", "美食", "购物", "拍照", "打卡", "纪念品"));
// 美食相关词汇
themeRelatedWords.put("美食", Arrays.asList("美食", "烹饪", "烘焙", "餐厅", "外卖", "食材", "食谱",
"菜品", "口味", "味道", "营养", "健康", "养生", "减肥"));
// 健康相关词汇
themeRelatedWords.put("健康", Arrays.asList("健康", "医疗", "养生", "健身", "锻炼", "饮食", "睡眠",
"休息", "放松", "压力", "心理", "情绪", "疾病", "治疗"));
// 运动相关词汇
themeRelatedWords.put("运动", Arrays.asList("运动", "健身", "锻炼", "瑜伽", "跑步", "游泳", "篮球",
"足球", "羽毛球", "乒乓球", "网球", "排球", "健身操", "舞蹈"));
// 训练相关词汇
themeRelatedWords.put("训练", Arrays.asList("训练", "上肢", "下肢", "核心", "拉伸", "力量", "耐力",
"有氧", "无氧", "器械", "自由重量", "俯卧撑", "仰卧起坐", "深蹲"));
// 购物相关词汇
themeRelatedWords.put("购物", Arrays.asList("购物", "商品", "价格", "优惠", "促销", "订单", "物流",
"快递", "收货", "退货", "换货", "评价", "评分", "客服"));
// 阅读相关词汇
themeRelatedWords.put("阅读", Arrays.asList("阅读", "书籍", "文章", "小说", "散文", "诗歌", "传记",
"历史", "哲学", "科学", "技术", "杂志", "报纸", "电子书"));
// 写作相关词汇
themeRelatedWords.put("写作", Arrays.asList("写作", "文章", "小说", "散文", "诗歌", "传记", "历史",
"论文", "报告", "文档", "策划", "方案", "总结", "日记"));
// 统计每个主题相关词汇出现的次数
int[] counts = new int[themes.length];
for (int i = 0; i < themes.length; i++) {
String theme = themes[i];
// 检查主题词本身
if (content.contains(theme)) {
counts[i] += 3; // 主题词本身权重最高
}
// 检查主题相关词汇
List<String> relatedWords = themeRelatedWords.get(theme);
if (relatedWords != null) {
for (String word : relatedWords) {
if (content.contains(word)) {
counts[i]++;
}
}
}
}
// 找到出现次数最多的主题
int maxIndex = 0;
for (int i = 1; i < counts.length; i++) {
if (counts[i] > counts[maxIndex]) {
maxIndex = i;
}
}
// 如果没有找到主题,返回通用主题
if (counts[maxIndex] == 0) {
return "笔记";
}
return themes[maxIndex];
}
/**
*
* @param content
* @return
*/
private String analyzeContentType(String content) {
// 检查是否包含时间安排相关词汇,添加"上午"关键词
if (content.contains("上午") || content.contains("早上") || content.contains("下午") || content.contains("晚上") ||
content.contains("明天") || content.contains("今天") || content.contains("后天") ||
content.contains("周一") || content.contains("周二") || content.contains("周三") ||
content.contains("周四") || content.contains("周五") || content.contains("周六") ||
content.contains("周日")) {
return "schedule";
}
// 检查是否包含任务列表相关词汇
if (content.contains("需要") || content.contains("要") || content.contains("必须") ||
content.contains("应该") || content.contains("完成") || content.contains("做")) {
return "task";
}
// 检查是否包含总结相关词汇
if (content.contains("总结") || content.contains("回顾") || content.contains("反思") ||
content.contains("收获") || content.contains("体会") || content.contains("感悟")) {
return "summary";
}
// 默认类型
return "normal";
}
/**
*
* @param content
* @param theme
* @param contentType
* @return
*/
private String generateTitleByThemeAndType(String content, String theme, String contentType) {
switch (contentType) {
case "schedule":
return generateScheduleTitle(content, theme);
case "task":
return generateTaskTitle(content, theme);
case "summary":
return generateSummaryTitle(content, theme);
default:
return generateNormalTitle(content, theme);
}
}
/**
*
* @param content
* @param theme
* @return
*/
private String generateScheduleTitle(String content, String theme) {
// 提取时间关键词
String timeKeywords = extractTimeKeywords(content);
// 优化标题生成逻辑
if (!TextUtils.isEmpty(timeKeywords)) {
// 生成更自然的标题格式:时间 + 主题 + 安排
return timeKeywords + theme + "安排";
} else {
// 没有时间关键词,使用默认格式
return theme + "安排";
}
}
/**
*
* @param content
* @param theme
* @return
*/
private String generateTaskTitle(String content, String theme) {
// 提取任务数量
int taskCount = countTasks(content);
// 提取主要任务
String mainTask = extractMainActivity(content, theme);
if (taskCount > 0 && !TextUtils.isEmpty(mainTask)) {
return theme + ": " + mainTask + "等" + taskCount + "项任务";
} else if (taskCount > 0) {
return theme + ": " + taskCount + "项任务";
} else if (!TextUtils.isEmpty(mainTask)) {
return theme + ": " + mainTask;
} else {
return theme + "任务";
}
}
/**
*
* @param content
* @param theme
* @return
*/
private String generateSummaryTitle(String content, String theme) {
// 提取时间关键词
String timeKeywords = extractTimeKeywords(content);
if (!TextUtils.isEmpty(timeKeywords)) {
return theme + ": " + timeKeywords + "总结";
} else {
return theme + "总结";
}
}
/**
*
* @param content
* @param theme
* @return
*/
private String generateNormalTitle(String content, String theme) {
// 提取主要活动
String mainActivity = extractMainActivity(content, theme);
if (!TextUtils.isEmpty(mainActivity)) {
return theme + ": " + mainActivity;
} else {
return theme + "笔记";
}
}
/**
*
* @param content
* @return
*/
private String extractTimeKeywords(String content) {
// 时间关键词优先级排序
String[] timeWords = {
"今天", "明天", "后天", "昨天", "上周", "本周", "下周",
"本月", "下月", "今年", "明年",
"周一", "周二", "周三", "周四", "周五", "周六", "周日"
};
// 查找第一个时间关键词
for (String timeWord : timeWords) {
if (content.contains(timeWord)) {
return timeWord;
}
}
// 检查是否包含早上、下午、晚上等时间词汇
if (content.contains("早上") || content.contains("下午") || content.contains("晚上")) {
return "今日";
}
return "";
}
/**
*
* @param content
* @param theme
* @return
*/
private String extractMainActivity(String content, String theme) {
// 去掉主题词
String contentWithoutTheme = content.replace(theme, "").trim();
// 按换行符分割
String[] lines = contentWithoutTheme.split("\\n");
// 查找第一个非空行
for (String line : lines) {
String trimmedLine = line.trim();
if (!TextUtils.isEmpty(trimmedLine)) {
// 提取活动关键词
String activity = extractActivityFromLine(trimmedLine);
if (!TextUtils.isEmpty(activity)) {
return activity;
}
}
}
return "";
}
/**
*
* @param line
* @return
*/
private String extractActivityFromLine(String line) {
// 常见活动动词
String[] verbs = {
"学习", "工作", "会议", "讨论", "研究", "开发", "设计",
"阅读", "写作", "思考", "运动", "锻炼", "跑步", "游泳",
"吃饭", "睡觉", "休息", "旅行", "购物", "参观", "访问"
};
// 查找第一个动词
for (String verb : verbs) {
if (line.contains(verb)) {
// 提取动词后面的内容最多10个字符
int verbIndex = line.indexOf(verb);
String activity = line.substring(verbIndex);
if (activity.length() > 10) {
activity = activity.substring(0, 10);
}
return activity;
}
}
// 如果没有找到动词返回行的前10个字符
if (line.length() > 10) {
return line.substring(0, 10);
}
return line;
}
/**
*
* @param content
* @return
*/
private int countTasks(String content) {
// 按换行符分割
String[] lines = content.split("\\n");
int count = 0;
// 统计非空行数量
for (String line : lines) {
if (!TextUtils.isEmpty(line.trim())) {
count++;
}
}
return count;
}
/**
*
* @param content
* @param theme
* @return
*/
private List<String> extractCoreKeywords(String content, String theme) {
List<String> keywords = new ArrayList<>();
// 常见关键词库,按领域分类
Map<String, List<String>> keywordLibrary = new HashMap<>();
keywordLibrary.put("学习", new ArrayList<String>() {{
add("毛概"); add("系统工程"); add("数据结构"); add("数学"); add("英语");
add("物理"); add("化学"); add("生物"); add("历史"); add("地理");
add("政治"); add("语文"); add("计算机"); add("编程"); add("算法");
}});
keywordLibrary.put("工作", new ArrayList<String>() {{
add("会议"); add("报告"); add("项目"); add("计划"); add("总结");
add("方案"); add("设计"); add("开发"); add("测试"); add("上线");
}});
keywordLibrary.put("生活", new ArrayList<String>() {{
add("吃饭"); add("睡觉"); add("运动"); add("购物"); add("旅行");
add("电影"); add("音乐"); add("阅读"); add("游戏"); add("社交");
}});
// 获取当前主题相关的关键词库
List<String> themeKeywords = keywordLibrary.getOrDefault(theme, new ArrayList<>());
// 查找内容中包含的主题相关关键词
for (String keyword : themeKeywords) {
if (content.contains(keyword) && !keywords.contains(keyword)) {
keywords.add(keyword);
}
}
// 如果没有找到主题相关关键词,提取内容中的高频词
if (keywords.isEmpty()) {
// 简单的高频词提取逻辑
String[] words = content.split("\\s+");
Map<String, Integer> wordCount = new HashMap<>();
// 统计词频
for (String word : words) {
if (!TextUtils.isEmpty(word) && word.length() > 1 && !word.equals(theme)) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
}
// 按词频排序取前3个
wordCount.entrySet().stream()
.sorted((a, b) -> b.getValue().compareTo(a.getValue()))
.limit(3)
.forEach(entry -> keywords.add(entry.getKey()));
}
return keywords;
}
/**
*
* @param content
* @param theme
* @param contentType
* @param coreKeywords
* @return
*/
private String generateTitleByThemeTypeAndKeywords(String content, String theme, String contentType, List<String> coreKeywords) {
switch (contentType) {
case "schedule":
return generateEnhancedScheduleTitle(content, theme, coreKeywords);
case "task":
return generateEnhancedTaskTitle(content, theme, coreKeywords);
case "summary":
return generateEnhancedSummaryTitle(content, theme, coreKeywords);
default:
return generateEnhancedNormalTitle(content, theme, coreKeywords);
}
}
/**
*
* @param content
* @param theme
* @param coreKeywords
* @return
*/
private String generateEnhancedScheduleTitle(String content, String theme, List<String> coreKeywords) {
String timeKeywords = extractTimeKeywords(content);
if (!TextUtils.isEmpty(timeKeywords)) {
// 生成简洁的标题:时间 + 主题 + 安排
return timeKeywords + theme + "安排";
} else {
// 没有时间关键词,生成:主题 + 安排
return theme + "安排";
}
}
/**
*
* @param content
* @param theme
* @param coreKeywords
* @return
*/
private String generateEnhancedTaskTitle(String content, String theme, List<String> coreKeywords) {
// 生成简洁的标题:主题 + 任务
return theme + "任务";
}
/**
*
* @param content
* @param theme
* @param coreKeywords
* @return
*/
private String generateEnhancedSummaryTitle(String content, String theme, List<String> coreKeywords) {
String timeKeywords = extractTimeKeywords(content);
if (!TextUtils.isEmpty(timeKeywords)) {
// 生成简洁的标题:时间 + 主题 + 总结
return timeKeywords + theme + "总结";
} else {
// 没有时间关键词,生成:主题 + 总结
return theme + "总结";
}
}
/**
*
* @param content
* @param theme
* @param coreKeywords
* @return
*/
private String generateEnhancedNormalTitle(String content, String theme, List<String> coreKeywords) {
// 生成简洁的标题:主题 + 笔记
return theme + "笔记";
}
/**
*
* @param content
* @param maxLength
* @return
*/
private String getContentSummary(String content, int maxLength) {
if (content.length() <= maxLength) {
return content;
}
// 尝试在句子边界截断
for (int i = maxLength; i > 0; i--) {
char c = content.charAt(i);
if (c == '。' || c == '' || c == '' || c == '.' || c == '!' || c == '?') {
return content.substring(0, i + 1);
}
}
// 找不到合适的边界,直接截断
return content.substring(0, maxLength) + "...";
}
}

@ -313,7 +313,7 @@ public class BackupUtils {
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + ") OR "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {

@ -158,7 +158,7 @@ public class DataUtils {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLDER)},
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
@ -186,7 +186,7 @@ public class DataUtils {
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
@ -249,7 +249,7 @@ public class DataUtils {
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;

@ -1,96 +0,0 @@
/*
* 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.tool;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* SpanSpannable
*/
public class ImageSpanUtils {
/**
* SpannableJSON
*
* @param spannable Spannable
* @param content
* @return JSON
*/
public static String serializeImageInfo(Spannable spannable, String content) {
JSONArray jsonArray = new JSONArray();
// 获取所有图片Span
ImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), ImageSpan.class);
for (ImageSpan span : imageSpans) {
JSONObject spanObj = new JSONObject();
try {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
// 获取图片URI
Drawable drawable = span.getDrawable();
if (drawable != null) {
spanObj.put("type", "image");
spanObj.put("start", start);
spanObj.put("end", end);
// 注意ImageSpan通常不直接存储URI需要额外的机制来跟踪图片路径
// 这里仅为示例,实际实现需要根据具体情况调整
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return jsonArray.toString();
}
/**
* JSON
*
* @param context
* @param text
* @param imageJson JSON
* @return SpannableStringBuilder
*/
public static SpannableStringBuilder deserializeImageInfo(Context context, String text, String imageJson) {
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
if (imageJson == null || imageJson.isEmpty()) {
return spannable;
}
try {
// 这里需要根据实际的图片存储机制来实现
// 因为图片通常需要特殊处理,这里暂时返回原始文本
// 实际实现需要考虑如何存储和检索图片
return spannable;
} catch (Exception e) {
e.printStackTrace();
}
return spannable;
}
}

@ -1,352 +0,0 @@
package net.micode.notes.tool;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
/**
* 便
*/
public class PrivacyLockManager {
private static final String TAG = "PrivacyLockManager";
// 锁类型常量
public static final int LOCK_TYPE_NONE = 0;
public static final int LOCK_TYPE_PASSWORD = 1;
public static final int LOCK_TYPE_GESTURE = 2;
// 锁状态常量
public static final int LOCK_STATUS_UNLOCKED = 0;
public static final int LOCK_STATUS_LOCKED = 1;
private Context mContext;
public PrivacyLockManager(Context context) {
this.mContext = context;
}
/**
* SHA256
* @param password
* @return
*/
public static String encryptPassword(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (Exception ex) {
Log.e(TAG, "Error encrypting password", ex);
return "";
}
}
/**
* 便
* @param noteId 便ID
* @return
*/
public boolean isNoteLocked(long noteId) {
String[] projection = new String[]{NoteColumns.LOCKED};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isLocked = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
isLocked = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCKED)) == LOCK_STATUS_LOCKED;
}
cursor.close();
}
return isLocked;
}
/**
* 便
* @param noteId 便ID
* @param lockType
* @param passwordOrGesture 使
* @return
*/
public boolean addPrivacyLock(long noteId, int lockType, String passwordOrGesture) {
if (noteId <= 0) {
Log.e(TAG, "Invalid note ID: " + noteId);
return false;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.LOCKED, LOCK_STATUS_LOCKED);
values.put(NoteColumns.LOCK_TYPE, lockType);
if ((lockType == LOCK_TYPE_PASSWORD || lockType == LOCK_TYPE_GESTURE) && passwordOrGesture != null) {
values.put(NoteColumns.ENCRYPTED_PASSWORD, passwordOrGesture);
} else if (lockType == LOCK_TYPE_NONE) {
values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
}
int rowsUpdated = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + " = ?",
new String[]{String.valueOf(noteId)}
);
return rowsUpdated > 0;
}
/**
* 便
* @param noteId 便ID
* @return
*/
public boolean removePrivacyLock(long noteId) {
if (noteId <= 0) {
Log.e(TAG, "Invalid note ID: " + noteId);
return false;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.LOCKED, LOCK_STATUS_UNLOCKED);
values.put(NoteColumns.LOCK_TYPE, LOCK_TYPE_NONE);
values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
int rowsUpdated = mContext.getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + " = ?",
new String[]{String.valueOf(noteId)}
);
return rowsUpdated > 0;
}
/**
*
* @param noteId 便ID
* @param password
* @return
*/
public boolean verifyPassword(long noteId, String password) {
String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isValid = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
String storedEncryptedPassword = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
String encryptedInput = encryptPassword(password);
isValid = storedEncryptedPassword.equals(encryptedInput);
}
cursor.close();
}
return isValid;
}
/**
* 便
* @param noteId 便ID
* @return
*/
public int getLockType(long noteId) {
String[] projection = new String[]{NoteColumns.LOCK_TYPE};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
int lockType = LOCK_TYPE_NONE;
if (cursor != null) {
if (cursor.moveToFirst()) {
lockType = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCK_TYPE));
}
cursor.close();
}
return lockType;
}
/**
* 便
* @param folderId ID
* @param lockType
* @param passwordOrGesture 使
* @return
*/
public boolean addPrivacyLockToFolder(long folderId, int lockType, String passwordOrGesture) {
// 首先为文件夹本身添加锁
boolean folderSuccess = addPrivacyLock(folderId, lockType, passwordOrGesture);
// 然后为文件夹内的所有便签添加锁
String[] projection = new String[]{NoteColumns.ID};
String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean allNotesSuccess = true;
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
boolean noteSuccess = addPrivacyLock(noteId, lockType, passwordOrGesture);
if (!noteSuccess) {
allNotesSuccess = false;
}
}
cursor.close();
}
return folderSuccess && allNotesSuccess;
}
/**
* 便
* @param folderId ID
* @return
*/
public boolean removePrivacyLockFromFolder(long folderId) {
// 首先移除文件夹本身的锁
boolean folderSuccess = removePrivacyLock(folderId);
// 然后移除文件夹内的所有便签的锁
String[] projection = new String[]{NoteColumns.ID};
String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean allNotesSuccess = true;
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
boolean noteSuccess = removePrivacyLock(noteId);
if (!noteSuccess) {
allNotesSuccess = false;
}
}
cursor.close();
}
return folderSuccess && allNotesSuccess;
}
/**
*
* @param noteId 便ID
* @param gestureSequence
* @return
*/
public boolean verifyGesture(long noteId, List<Integer> gestureSequence) {
String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
String selection = NoteColumns.ID + " = ?";
String[] selectionArgs = new String[]{String.valueOf(noteId)};
Cursor cursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
projection,
selection,
selectionArgs,
null
);
boolean isValid = false;
if (cursor != null) {
if (cursor.moveToFirst()) {
String storedGestureSequence = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
// 将手势序列转换为字符串进行比较
StringBuilder gestureBuilder = new StringBuilder();
for (int point : gestureSequence) {
gestureBuilder.append(point);
}
String inputGestureSequence = gestureBuilder.toString();
isValid = storedGestureSequence.equals(inputGestureSequence);
}
cursor.close();
}
return isValid;
}
/**
*
* @param gesturePoints
* @return
*/
public static String gestureToString(List<Integer> gesturePoints) {
StringBuilder sb = new StringBuilder();
for (int point : gesturePoints) {
sb.append(point);
}
return sb.toString();
}
/**
*
* @param gestureString
* @return
*/
public static List<Integer> stringToGesture(String gestureString) {
List<Integer> gesturePoints = new ArrayList<>();
for (char c : gestureString.toCharArray()) {
gesturePoints.add(Character.getNumericValue(c));
}
return gesturePoints;
}
}

@ -1,107 +0,0 @@
package net.micode.notes.tool;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.text.style.StrikethroughSpan;
import android.graphics.Typeface;
import android.text.ParcelableSpan;
import android.text.style.CharacterStyle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Spannable
*/
public class RichTextFormatUtils {
/**
* SpannableJSON
*
* @param spannable Spannable
* @return JSON
*/
public static String serializeFormatInfo(Spannable spannable) {
JSONArray jsonArray = new JSONArray();
CharacterStyle[] spans = spannable.getSpans(0, spannable.length(), CharacterStyle.class);// 获取所有字符样式Span
for (CharacterStyle span : spans) {
JSONObject spanObj = new JSONObject();
try {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
int flags = spannable.getSpanFlags(span);
// 识别Span类型并保存相关信息
if (span instanceof StyleSpan) {
StyleSpan styleSpan = (StyleSpan) span;
spanObj.put("type", "style");
spanObj.put("style", styleSpan.getStyle());
} else if (span instanceof UnderlineSpan) {
spanObj.put("type", "underline");
} else if (span instanceof StrikethroughSpan) {
spanObj.put("type", "strikethrough");
} else {
continue; // 不支持的Span类型跳过
}
spanObj.put("start", start);
spanObj.put("end", end);
spanObj.put("flags", flags);
jsonArray.put(spanObj);
} catch (JSONException e) {
e.printStackTrace();
}
}
return jsonArray.toString();
}
/**
* JSON
*
* @param text
* @param formatJson JSON
* @return SpannableStringBuilder
*/
public static SpannableStringBuilder deserializeFormatInfo(String text, String formatJson) {
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
if (formatJson == null || formatJson.isEmpty()) {
return spannable;
}
try {
JSONArray jsonArray = new JSONArray(formatJson);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject spanObj = jsonArray.getJSONObject(i);
int start = spanObj.getInt("start");
int end = spanObj.getInt("end");
int flags = spanObj.getInt("flags");
String type = spanObj.getString("type");
CharacterStyle span = null;
if ("style".equals(type)) {
int style = spanObj.getInt("style");
span = new StyleSpan(style);
} else if ("underline".equals(type)) {
span = new UnderlineSpan();
} else if ("strikethrough".equals(type)) {
span = new StrikethroughSpan();
}
if (span != null && start >= 0 && end <= text.length() && start < end) {
spannable.setSpan(span, start, end, flags);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return spannable;
}
}

@ -1,503 +0,0 @@
package net.micode.notes.tool;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*
*/
public class SmartReminderManager {
private static final String TAG = "SmartReminderManager";
private Context mContext;
// 日期时间格式
private SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 时间相关的正则表达式
private Pattern mTimePattern = Pattern.compile(
"(\\d{1,2})[:]\\d{1,2}|" + // 如 3:30 或 1430
"(\\d{1,2})[点时]|" + // 如 3点 或 14时
"上午(\\d{1,2})[点时]|" + // 如 上午3点 或 上午10时
"下午(\\d{1,2})[点时]|" + // 如 下午3点 或 下午10时
"明天|今天|后天|" + // 如 明天
"(\\d{1,2})月(\\d{1,2})[日号]|" + // 如 12月31日 或 1月1号
"下[周月年]|本[周月年]|上[周月年]"); // 如 下周 或 本月
// 重复相关的正则表达式
private Pattern mRepeatPattern = Pattern.compile(
"每[天周月年]|" + // 如 每天 或 每周
"周[一二三四五六日]|" + // 如 周一 或 周三
"每月(\\d{1,2})[日号]|" + // 如 每月15日
"每年(\\d{1,2})月(\\d{1,2})[日号]"); // 如 每年12月31日
// 关联关键词
private List<String> mRelatedKeywords = new ArrayList<>();
public SmartReminderManager(Context context) {
mContext = context;
// 初始化关联关键词
mRelatedKeywords.add("会议");
mRelatedKeywords.add("面试");
mRelatedKeywords.add("任务");
mRelatedKeywords.add("计划");
mRelatedKeywords.add("安排");
mRelatedKeywords.add("项目");
mRelatedKeywords.add("工作");
mRelatedKeywords.add("进度");
mRelatedKeywords.add("报告");
mRelatedKeywords.add("讨论");
mRelatedKeywords.add("准备");
mRelatedKeywords.add("总结");
mRelatedKeywords.add("方案");
mRelatedKeywords.add("设计");
mRelatedKeywords.add("开发");
mRelatedKeywords.add("测试");
mRelatedKeywords.add("上线");
mRelatedKeywords.add(" deadline");
mRelatedKeywords.add("截止");
}
/**
* 便
* @param content 便
* @return 0
*/
public long suggestReminderTime(String content) {
if (TextUtils.isEmpty(content)) {
return 0;
}
// 先查找日期关键词明天、后天、x月x日等
Matcher dateMatcher = Pattern.compile("明天|今天|后天|(\\d{1,2})月(\\d{1,2})[日号]|下[周月年]|本[周月年]|上[周月年]").matcher(content);
String dateStr = null;
if (dateMatcher.find()) {
dateStr = dateMatcher.group();
}
// 再查找时间关键词3:30、3点、上午3点等
Matcher timeMatcher = Pattern.compile("(\\d{1,2})[:]\\d{1,2}|(\\d{1,2})[点时]|上午(\\d{1,2})[点时]|下午(\\d{1,2})[点时]").matcher(content);
String timeStr = null;
if (timeMatcher.find()) {
timeStr = timeMatcher.group();
}
// 结合日期和时间进行解析,生成更准确的提醒时间
if (dateStr != null && timeStr != null) {
// 先解析日期,获取基准日期
long baseTime = parseTimeString(dateStr);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(baseTime);
// 再解析时间,设置具体时分
if (timeStr.contains(":") || timeStr.contains("")) {
// 处理时间格式,如 3:30 或 1430
String[] timeParts = timeStr.replace("", ":").split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
} else if (timeStr.matches("\\d{1,2}[点时]")) {
// 处理时间格式,如 3点 或 14时
int hour = Integer.parseInt(timeStr.replaceAll("[点时]", ""));
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
} else if (timeStr.matches("上午\\d{1,2}[点时]")) {
// 处理上午时间,如 上午3点 或 上午10时
int hour = Integer.parseInt(timeStr.replaceAll("[上午点时]", ""));
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
} else if (timeStr.matches("下午\\d{1,2}[点时]")) {
// 处理下午时间,如 下午3点 或 下午10时
int hour = Integer.parseInt(timeStr.replaceAll("[下午点时]", ""));
// 转换为24小时制
hour += 12;
// 特殊处理下午12点
if (hour == 24) {
hour = 12;
}
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
}
calendar.set(Calendar.SECOND, 0);
return calendar.getTimeInMillis();
}
// 如果只有日期或只有时间,使用原有逻辑
else {
Matcher matcher = mTimePattern.matcher(content);
if (matcher.find()) {
String singleTimeStr = matcher.group();
return parseTimeString(singleTimeStr);
}
}
return 0;
}
/**
* 便
* @param content 便
* @return "daily", "weekly", "monthly", "yearly"
*/
public String suggestRepeatType(String content) {
if (TextUtils.isEmpty(content)) {
return "";
}
// 查找重复相关关键词
Matcher repeatMatcher = mRepeatPattern.matcher(content);
if (repeatMatcher.find()) {
String repeatStr = repeatMatcher.group();
return parseRepeatString(repeatStr);
}
return "";
}
/**
* 便
* @param content1 便
* @param content2 便
* @return truefalse
*/
public boolean areNotesRelated(String content1, String content2) {
if (TextUtils.isEmpty(content1) || TextUtils.isEmpty(content2)) {
return false;
}
// 转换为小写,不区分大小写
String lowerContent1 = content1.toLowerCase();
String lowerContent2 = content2.toLowerCase();
// 1. 检查是否包含相同的关联关键词
for (String keyword : mRelatedKeywords) {
if (lowerContent1.contains(keyword) && lowerContent2.contains(keyword)) {
return true;
}
}
// 2. 检查是否包含相似的项目名称或主题
// 查找可能的项目名称(如"项目A"、"任务1"等)
Pattern projectPattern = Pattern.compile("[项目任务计划][A-Za-z0-9_]+|[A-Za-z0-9_]+[项目任务计划]");
Matcher projectMatcher1 = projectPattern.matcher(lowerContent1);
Matcher projectMatcher2 = projectPattern.matcher(lowerContent2);
List<String> projects1 = new ArrayList<>();
while (projectMatcher1.find()) {
projects1.add(projectMatcher1.group());
}
while (projectMatcher2.find()) {
if (projects1.contains(projectMatcher2.group())) {
return true;
}
}
// 3. 检查是否包含相同或相似的时间信息
// 先提取所有时间相关的关键词
Matcher timeMatcher1 = mTimePattern.matcher(content1);
Matcher timeMatcher2 = mTimePattern.matcher(content2);
List<String> timeStrs1 = new ArrayList<>();
while (timeMatcher1.find()) {
timeStrs1.add(timeMatcher1.group());
}
List<String> timeStrs2 = new ArrayList<>();
while (timeMatcher2.find()) {
timeStrs2.add(timeMatcher2.group());
}
// 检查是否有完全相同的时间关键词
for (String timeStr : timeStrs1) {
if (timeStrs2.contains(timeStr)) {
return true;
}
}
// 检查是否有相关的时间关键词(如"明天"和"明天3点"
for (String time1 : timeStrs1) {
for (String time2 : timeStrs2) {
if (areTimesRelated(time1, time2)) {
return true;
}
}
}
// 4. 检查内容相似度(如果内容较短,相似度要求较高)
int minLength = Math.min(content1.length(), content2.length());
if (minLength > 5) { // 只有当内容足够长时才检查相似度
int commonWords = countCommonWords(content1, content2);
double similarity = (double) commonWords / Math.sqrt(content1.length() * content2.length());
if (similarity > 0.3) { // 相似度阈值
return true;
}
}
return false;
}
/**
*
* @param time1
* @param time2
* @return truefalse
*/
private boolean areTimesRelated(String time1, String time2) {
// 检查是否有包含关系(如"明天"包含在"明天3点"中)
if (time1.contains(time2) || time2.contains(time1)) {
return true;
}
// 检查是否都是日期或都是时间
boolean isDate1 = time1.matches(".*[明天后天今天月日号周].*");
boolean isDate2 = time2.matches(".*[明天后天今天月日号周].*");
boolean isTime1 = time1.matches(".*[0-9::点时上午下午].*");
boolean isTime2 = time2.matches(".*[0-9::点时上午下午].*");
// 如果一个是日期,一个是时间,认为它们可能相关
if ((isDate1 && isTime2) || (isDate2 && isTime1)) {
return true;
}
return false;
}
/**
*
* @param str1
* @param str2
* @return
*/
private int countCommonWords(String str1, String str2) {
// 简单的词语分割,按空格分割
String[] words1 = str1.split("\\s+");
String[] words2 = str2.split("\\s+");
Set<String> wordSet1 = new HashSet<>();
for (String word : words1) {
// 移除常见标点符号
String cleanedWord = word.replaceAll("[\\p{Punct}]", "").toLowerCase();
if (cleanedWord.length() > 1) { // 只考虑长度大于1的词语
wordSet1.add(cleanedWord);
}
}
int commonCount = 0;
for (String word : words2) {
// 移除常见标点符号
String cleanedWord = word.replaceAll("[\\p{Punct}]", "").toLowerCase();
if (cleanedWord.length() > 1 && wordSet1.contains(cleanedWord)) {
commonCount++;
}
}
return commonCount;
}
/**
*
* @param timeStr
* @return
*/
private long parseTimeString(String timeStr) {
Calendar calendar = Calendar.getInstance();
try {
if (timeStr.contains(":") || timeStr.contains("")) {
// 处理时间格式,如 3:30 或 1430
String[] timeParts = timeStr.replace("", ":").split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
// 如果时间已过,设置为明天
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
} else if (timeStr.matches("\\d{1,2}[点时]")) {
// 处理时间格式,如 3点 或 14时
int hour = Integer.parseInt(timeStr.replaceAll("[点时]", ""));
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// 如果时间已过,设置为明天
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
} else if (timeStr.matches("上午\\d{1,2}[点时]")) {
// 处理上午时间,如 上午3点 或 上午10时
int hour = Integer.parseInt(timeStr.replaceAll("[上午点时]", ""));
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// 如果时间已过,设置为明天
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
} else if (timeStr.matches("下午\\d{1,2}[点时]")) {
// 处理下午时间,如 下午3点 或 下午10时
int hour = Integer.parseInt(timeStr.replaceAll("[下午点时]", ""));
// 转换为24小时制
hour += 12;
// 特殊处理下午12点
if (hour == 24) {
hour = 12;
}
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// 如果时间已过,设置为明天
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
} else if (timeStr.contains("明天")) {
// 明天
calendar.add(Calendar.DAY_OF_MONTH, 1);
} else if (timeStr.contains("后天")) {
// 后天
calendar.add(Calendar.DAY_OF_MONTH, 2);
} else if (timeStr.contains("今天")) {
// 今天(保持不变)
} else if (timeStr.contains("周")) {
// 处理星期,如 下周 或 本周
int dayOfWeek = getDayOfWeek(timeStr);
if (dayOfWeek != -1) {
int currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
int daysToAdd = dayOfWeek - currentDayOfWeek;
if (daysToAdd <= 0) {
daysToAdd += 7;
}
calendar.add(Calendar.DAY_OF_MONTH, daysToAdd);
}
} else if (timeStr.contains("月")) {
// 处理日期,如 12月31日
String[] dateParts = timeStr.replace("月", "-").replace("日", "").replace("号", "").split("-");
int month = Integer.parseInt(dateParts[0]) - 1; // Calendar月份从0开始
int day = Integer.parseInt(dateParts[1]);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, day);
// 如果日期已过,设置为明年
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.YEAR, 1);
}
}
return calendar.getTimeInMillis();
} catch (Exception e) {
Log.e(TAG, "Failed to parse time string: " + timeStr, e);
return 0;
}
}
/**
*
* @param repeatStr
* @return
*/
private String parseRepeatString(String repeatStr) {
if (repeatStr.contains("每天")) {
return "每天";
} else if (repeatStr.contains("每周")) {
return "每周";
} else if (repeatStr.contains("每月")) {
return "每月";
} else if (repeatStr.contains("每年")) {
return "每年";
} else if (repeatStr.contains("周一")) {
return "每周一";
} else if (repeatStr.contains("周二")) {
return "每周二";
} else if (repeatStr.contains("周三")) {
return "每周三";
} else if (repeatStr.contains("周四")) {
return "每周四";
} else if (repeatStr.contains("周五")) {
return "每周五";
} else if (repeatStr.contains("周六")) {
return "每周六";
} else if (repeatStr.contains("周日")) {
return "每周日";
} else if (repeatStr.contains("周")) {
return "每周";
}
return "";
}
/**
* Calendar.DAY_OF_WEEK
* @param dayStr
* @return Calendar.DAY_OF_WEEK-1
*/
private int getDayOfWeek(String dayStr) {
if (dayStr.contains("周一") || dayStr.contains("星期一")) {
return Calendar.MONDAY;
} else if (dayStr.contains("周二") || dayStr.contains("星期二")) {
return Calendar.TUESDAY;
} else if (dayStr.contains("周三") || dayStr.contains("星期三")) {
return Calendar.WEDNESDAY;
} else if (dayStr.contains("周四") || dayStr.contains("星期四")) {
return Calendar.THURSDAY;
} else if (dayStr.contains("周五") || dayStr.contains("星期五")) {
return Calendar.FRIDAY;
} else if (dayStr.contains("周六") || dayStr.contains("星期六")) {
return Calendar.SATURDAY;
} else if (dayStr.contains("周日") || dayStr.contains("星期日")) {
return Calendar.SUNDAY;
}
return -1;
}
/**
*
* @param currentTime
* @param repeatType
* @return
*/
public long getNextRepeatTime(long currentTime, String repeatType) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(currentTime);
switch (repeatType) {
case "daily":
calendar.add(Calendar.DAY_OF_MONTH, 1);
break;
case "weekly":
calendar.add(Calendar.WEEK_OF_MONTH, 1);
break;
case "monthly":
calendar.add(Calendar.MONTH, 1);
break;
case "yearly":
calendar.add(Calendar.YEAR, 1);
break;
default:
return 0;
}
return calendar.getTimeInMillis();
}
}

@ -1,62 +0,0 @@
package net.micode.notes.tool;
import android.content.Context;
import android.content.SharedPreferences;
/**
*
*/
public class UserManager {
private static final String PREF_NAME = "user_prefs";
private static final String KEY_IS_LOGGED_IN = "is_logged_in";
private static final String KEY_CURRENT_USER = "current_user";
private static UserManager instance;
private SharedPreferences sharedPreferences;
private UserManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public static synchronized UserManager getInstance(Context context) {
if (instance == null) {
instance = new UserManager(context.getApplicationContext());
}
return instance;
}
/**
*
*/
public void saveLoginStatus(String username) {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, true)
.putString(KEY_CURRENT_USER, username)
.apply();
}
/**
*
*/
public boolean isLoggedIn() {
return sharedPreferences.getBoolean(KEY_IS_LOGGED_IN, false);
}
/**
*
*/
public String getCurrentUsername() {
return sharedPreferences.getString(KEY_CURRENT_USER, "");
}
/**
* 退
*/
public void logout() {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, false)
.remove(KEY_CURRENT_USER)
.apply();
}
}

@ -73,12 +73,7 @@ public class AlarmInitReceiver extends BroadcastReceiver {
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
// 创建待定意图,用于闹钟管理器
// 在 Android 12+ 上必须指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE
int flags = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, flags);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取闹钟管理器服务
AlarmManager alarmManager = (AlarmManager) context

@ -1,264 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class GestureLockView extends View {
private static final int MATRIX_SIZE = 3; // 3x3矩阵
private Paint mPaint; // 画笔
private List<GesturePoint> mPoints; // 手势点集合
private List<Integer> mSelectedPoints; // 已选择的点集合
private List<GestureLine> mLines; // 连接线集合
private GesturePoint mCurrentPoint; // 当前手指位置点
private boolean mIsDrawing; // 是否正在绘制
private OnGestureCompleteListener mListener; // 手势完成监听器
public interface OnGestureCompleteListener {
void onGestureComplete(List<Integer> selectedPoints);
}
public static class GesturePoint {
public float x; // X坐标
public float y; // Y坐标
public int index; // 点的索引 (0-8)
public boolean isSelected; // 是否被选中
public GesturePoint(float x, float y, int index) {
this.x = x;
this.y = y;
this.index = index;
this.isSelected = false;
}
}
public static class GestureLine {
public GesturePoint startPoint; // 起始点
public GesturePoint endPoint; // 结束点
public GestureLine(GesturePoint start, GesturePoint end) {
this.startPoint = start;
this.endPoint = end;
}
}
public GestureLockView(Context context) {
super(context);
init();
}
public GestureLockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true); // 抗锯齿
mPaint.setStrokeWidth(4); // 线宽
mPoints = new ArrayList<>();
mSelectedPoints = new ArrayList<>();
mLines = new ArrayList<>();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 计算点的位置
int padding = Math.min(w, h) / 10; // 边距
int cellWidth = (w - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的宽度
int cellHeight = (h - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的高度
mPoints.clear();
for (int i = 0; i < MATRIX_SIZE; i++) {
for (int j = 0; j < MATRIX_SIZE; j++) {
float x = padding + j * cellWidth;
float y = padding + i * cellHeight;
mPoints.add(new GesturePoint(x, y, i * MATRIX_SIZE + j));
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制所有点
for (GesturePoint point : mPoints) {
drawPoint(canvas, point);
}
// 绘制连接线
for (GestureLine line : mLines) {
drawLine(canvas, line);
}
// 如果正在绘制,绘制从最后一点到当前手指位置的连线
if (mIsDrawing && mCurrentPoint != null && !mSelectedPoints.isEmpty()) {
GesturePoint lastPoint = mPoints.get(mSelectedPoints.get(mSelectedPoints.size() - 1));
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(lastPoint.x, lastPoint.y, mCurrentPoint.x, mCurrentPoint.y, mPaint);
}
}
private void drawPoint(Canvas canvas, GesturePoint point) {
float radius = Math.min(getWidth(), getHeight()) / 15f; // 点的半径
if (point.isSelected) {
// 绘制选中的点(大圆圈)
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(point.x, point.y, radius, mPaint);
// 绘制内部小圆点
mPaint.setColor(Color.WHITE);
canvas.drawCircle(point.x, point.y, radius / 2, mPaint);
} else {
// 绘制未选中的点(圆环)
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
canvas.drawCircle(point.x, point.y, radius, mPaint);
}
}
private void drawLine(Canvas canvas, GestureLine line) {
mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(8);
canvas.drawLine(line.startPoint.x, line.startPoint.y,
line.endPoint.x, line.endPoint.y, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleTouchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
handleTouchMove(x, y);
break;
case MotionEvent.ACTION_UP:
handleTouchUp();
break;
}
invalidate(); // 重绘
return true;
}
private void handleTouchDown(float x, float y) {
mSelectedPoints.clear();
mLines.clear();
mIsDrawing = true;
// 检查是否点击了某个点
for (GesturePoint point : mPoints) {
float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
selectPoint(point);
break;
}
}
}
private void handleTouchMove(float x, float y) {
if (!mIsDrawing) return;
// 更新当前手指位置
mCurrentPoint = new GesturePoint(x, y, -1);
// 检查是否有新的点被经过
for (GesturePoint point : mPoints) {
if (point.isSelected) continue; // 已经选过的点不再处理
float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
selectPoint(point);
break;
}
}
}
private void handleTouchUp() {
if (!mIsDrawing) return;
mIsDrawing = false;
mCurrentPoint = null;
// 通知手势完成
if (mListener != null && !mSelectedPoints.isEmpty()) {
mListener.onGestureComplete(mSelectedPoints);
}
// 重置状态
reset();
}
private void selectPoint(GesturePoint point) {
if (!point.isSelected) {
point.isSelected = true;
mSelectedPoints.add(point.index);
// 添加连接线(如果不是第一个点)
if (mSelectedPoints.size() > 1) {
int lastIndex = mSelectedPoints.get(mSelectedPoints.size() - 2);
GesturePoint lastPoint = mPoints.get(lastIndex);
mLines.add(new GestureLine(lastPoint, point));
}
}
}
private void reset() {
for (GesturePoint point : mPoints) {
point.isSelected = false;
}
mSelectedPoints.clear();
mLines.clear();
mCurrentPoint = null;
}
public void setOnGestureCompleteListener(OnGestureCompleteListener listener) {
this.mListener = listener;
}
/**
*
*/
public void clearGesture() {
reset();
invalidate();
}
/**
*
*/
public int getSelectedPointsCount() {
return mSelectedPoints.size();
}
}

@ -1,340 +0,0 @@
package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import net.micode.notes.R;
import net.micode.notes.data.UserDatabaseHelper;
import net.micode.notes.tool.UserManager;
public class LoginRegisterActivity extends AppCompatActivity {
// UI组件
private TextView tvLoginTab;
private TextView tvRegisterTab;
private View tabIndicator;
private EditText etUsername;
private EditText etPassword;
private ImageView ivPasswordVisibility;
private CheckBox cbRememberPassword;
private TextView tvErrorMessage;
private Button btnAction;
private TextView tvForgotPassword;
// 数据库帮助类
private UserDatabaseHelper dbHelper;
// 用户管理器
private UserManager userManager;
// 当前状态true为登录false为注册
private boolean isLoginMode = true;
// 密码可见性状态
private boolean isPasswordVisible = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_register);
// 初始化用户管理器
userManager = UserManager.getInstance(this);
// 每次都显示登录/注册界面,不自动登录
initViews();
initListeners();
updateUIForLoginMode();
}
/**
*
*/
private void initViews() {
tvLoginTab = findViewById(R.id.tv_login_tab);
tvRegisterTab = findViewById(R.id.tv_register_tab);
tabIndicator = findViewById(R.id.tab_indicator);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
ivPasswordVisibility = findViewById(R.id.iv_password_visibility);
cbRememberPassword = findViewById(R.id.cb_remember_password);
tvErrorMessage = findViewById(R.id.tv_error_message);
btnAction = findViewById(R.id.btn_action);
tvForgotPassword = findViewById(R.id.tv_forgot_password);
// 初始化数据库帮助类
dbHelper = UserDatabaseHelper.getInstance(this);
// 加载记住的密码
loadRememberedPassword();
}
/**
*
*/
private void initListeners() {
// 登录标签点击
tvLoginTab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isLoginMode = true;
updateUIForLoginMode();
}
});
// 注册标签点击
tvRegisterTab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isLoginMode = false;
updateUIForLoginMode();
}
});
// 密码可见性切换
ivPasswordVisibility.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
togglePasswordVisibility();
}
});
// 登录/注册按钮点击
btnAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isLoginMode) {
performLogin();
} else {
performRegister();
}
}
});
// 忘记密码按钮点击
tvForgotPassword.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showForgotPasswordDialog();
}
});
}
/**
* UI
*/
private void updateUIForLoginMode() {
if (isLoginMode) {
// 登录模式
tvLoginTab.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.darker_gray));
// 更新指示器位置
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 40; // 左侧位置
tabIndicator.setLayoutParams(params);
btnAction.setText("登录");
tvForgotPassword.setVisibility(View.VISIBLE);
} else {
// 注册模式
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
tvLoginTab.setTextColor(getResources().getColor(android.R.color.darker_gray));
// 更新指示器位置
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 120; // 右侧位置
tabIndicator.setLayoutParams(params);
btnAction.setText("注册");
tvForgotPassword.setVisibility(View.GONE);
}
// 清除错误消息
tvErrorMessage.setVisibility(View.GONE);
}
/**
*
*/
private void togglePasswordVisibility() {
isPasswordVisible = !isPasswordVisible;
if (isPasswordVisible) {
etPassword.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
ivPasswordVisibility.setImageResource(android.R.drawable.ic_secure);
} else {
etPassword.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
ivPasswordVisibility.setImageResource(android.R.drawable.ic_lock_lock);
}
// 将光标移到末尾
etPassword.setSelection(etPassword.getText().length());
}
/**
*
*/
private void performLogin() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
showError("请输入用户名");
return;
}
if (TextUtils.isEmpty(password)) {
showError("请输入密码");
return;
}
// 验证用户凭据
if (dbHelper.authenticateUser(username, password)) {
// 登录成功,保存登录状态
userManager.saveLoginStatus(username);
// 保存记住的密码
if (cbRememberPassword.isChecked()) {
saveRememberedPassword(username, password);
} else {
clearRememberedPassword();
}
// 跳转到主页面
Intent intent = new Intent(this, NotesListActivity.class);
startActivity(intent);
finish();
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
} else {
showError("用户名或密码错误,请重新输入");
}
}
/**
*
*/
private void saveRememberedPassword(String username, String password) {
SharedPreferences sharedPreferences = getSharedPreferences("login_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("remember_password", true);
editor.putString("username", username);
editor.putString("password", password);
editor.apply();
}
/**
*
*/
private void loadRememberedPassword() {
SharedPreferences sharedPreferences = getSharedPreferences("login_prefs", Context.MODE_PRIVATE);
boolean rememberPassword = sharedPreferences.getBoolean("remember_password", false);
if (rememberPassword) {
String username = sharedPreferences.getString("username", "");
String password = sharedPreferences.getString("password", "");
etUsername.setText(username);
etPassword.setText(password);
cbRememberPassword.setChecked(true);
}
}
/**
*
*/
private void clearRememberedPassword() {
SharedPreferences sharedPreferences = getSharedPreferences("login_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("remember_password", false);
editor.remove("username");
editor.remove("password");
editor.apply();
}
/**
*
*/
private void performRegister() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
showError("请输入用户名");
return;
}
if (username.length() < 3) {
showError("用户名长度不能少于3位");
return;
}
if (TextUtils.isEmpty(password)) {
showError("请输入密码");
return;
}
if (password.length() < 6) {
showError("密码长度不能少于6位");
return;
}
// 检查用户名是否已存在
if (dbHelper.isUsernameExists(username)) {
showError("该用户名已注册,请直接登录");
return;
}
// 注册用户
boolean success = dbHelper.registerUser(username, password);
if (success) {
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
// 自动切换到登录模式
isLoginMode = true;
updateUIForLoginMode();
} else {
showError("注册失败,请重试");
}
}
/**
*
*/
private void showError(String message) {
tvErrorMessage.setText(message);
tvErrorMessage.setVisibility(View.VISIBLE);
}
/**
*
*/
private void showForgotPasswordDialog() {
new AlertDialog.Builder(this)
.setTitle("忘记密码")
.setMessage("请联系管理员重置密码(测试版)")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
}

File diff suppressed because it is too large Load Diff

@ -48,8 +48,7 @@ public class NoteItemData {
NoteColumns.SNIPPET, // 内容摘要/片段
NoteColumns.TYPE, // 类型(笔记、文件夹、系统文件夹等)
NoteColumns.WIDGET_ID, // 关联的小部件ID
NoteColumns.WIDGET_TYPE, // 小部件的类型
NoteColumns.TAG, // 标签
NoteColumns.WIDGET_TYPE, // 小部件类型
};
/**
@ -68,7 +67,6 @@ public class NoteItemData {
private static final int TYPE_COLUMN = 9; // 类型列的索引
private static final int WIDGET_ID_COLUMN = 10; // 小部件ID列的索引
private static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列的索引
private static final int TAG_COLUMN = 12; // 标签列的索引
// 成员变量,对应数据库中的各个字段
private long mId; // 笔记/文件夹的唯一标识ID
@ -83,8 +81,6 @@ public class NoteItemData {
private int mType; // 类型(笔记、文件夹、系统文件夹等)
private int mWidgetId; // 关联的桌面小部件ID
private int mWidgetType; // 桌面小部件的类型
private String mTag; // 标签
private boolean mIsCompleted; // 便签是否已完成
// 额外添加的业务逻辑相关字段(不直接来自数据库)
private String mName; // 联系人姓名(如果是通话记录笔记)
@ -114,9 +110,6 @@ public class NoteItemData {
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mTag = cursor.getString(TAG_COLUMN);
// 保存原始的完成状态
mIsCompleted = mSnippet.startsWith(NoteEditActivity.TAG_CHECKED);
// 清理摘要文本中的清单标记符号(对勾和方框),让显示更干净
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
@ -338,40 +331,8 @@ public class NoteItemData {
}
/**
*
* @return
*/
public String getTitle() {
if (TextUtils.isEmpty(mSnippet)) {
return "";
}
int firstLineEnd = mSnippet.indexOf('\n');
if (firstLineEnd == -1) {
// 如果只有一行,全部作为标题
return mSnippet;
}
return mSnippet.substring(0, firstLineEnd);
}
/**
*
* @return
*/
public String getContentSnippet() {
if (TextUtils.isEmpty(mSnippet)) {
return "";
}
int firstLineEnd = mSnippet.indexOf('\n');
if (firstLineEnd == -1) {
// 如果只有一行,内容为空
return "";
}
return mSnippet.substring(firstLineEnd + 1);
}
/**
*
* @return
* /
* @return
*/
public String getSnippet() {
return mSnippet;
@ -385,14 +346,6 @@ public class NoteItemData {
return (mAlertDate > 0);
}
/**
*
* @return
*/
public String getTag() {
return mTag;
}
/**
*
*
@ -401,14 +354,6 @@ public class NoteItemData {
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* 便
* @return 便true
*/
public boolean isCompleted() {
return mIsCompleted;
}
/**
*

@ -28,12 +28,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.net.Uri.Builder;
import android.content.ContentUris;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -45,10 +39,7 @@ import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -57,34 +48,25 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.VelocityTracker;
import android.view.inputmethod.InputMethodManager;
import android.text.InputType;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.SmartReminderManager;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.PrivacyLockManager;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
@ -102,9 +84,6 @@ import java.util.HashSet;
*/
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// NOTE_ID常量
public static final String NOTE_ID = "note_id";
// 异步查询的令牌,用于区分不同类型的查询
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 查询文件夹下的笔记列表
private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 查询可用的文件夹列表(用于移动操作)
@ -146,17 +125,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem; // 当前获得焦点的数据项(长按时记录)
private EditText mSearchEditText; // 搜索输入框
private ImageButton mSearchClearButton; // 搜索清除按钮
private String mSearchQuery = ""; // 当前搜索关键词
private String mCurrentTagFilter = ""; // 当前标签筛选
private SmartReminderManager mSmartReminderManager; // 智能提醒管理器
// 手势检测相关成员变量
private GestureDetector mGestureDetector; // 手势检测器
private static final int SWIPE_MIN_DISTANCE = 120; // 最小滑动距离
private static final int SWIPE_MAX_OFF_PATH = 250; // 最大偏离路径
private static final int SWIPE_THRESHOLD_VELOCITY = 200; // 最小滑动速度
// 数据库查询条件Selection
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; // 普通查询父文件夹ID=?
@ -185,30 +153,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* 使
*/
setAppInfoFromRawRes();
// 添加对OnBackPressedDispatcher的支持以便处理手势返回
try {
// 使用反射调用getOnBackPressedDispatcher方法添加一个空回调
// 这样可以确保手势返回功能正常工作,同时保持原有代码不变
Object dispatcher = getClass().getMethod("getOnBackPressedDispatcher").invoke(this);
Class<?> callbackClass = Class.forName("androidx.activity.OnBackPressedCallback");
Object callback = callbackClass.getConstructor(boolean.class).newInstance(true);
Class<?> callbackInterface = Class.forName("androidx.activity.OnBackPressedCallback$OnBackPressedListener");
Object listener = java.lang.reflect.Proxy.newProxyInstance(
callbackInterface.getClassLoader(),
new Class<?>[]{callbackInterface},
(proxy, method, args) -> {
// 调用原有的onBackPressed方法
onBackPressed();
return null;
}
);
callbackClass.getMethod("addOnBackPressedListener", callbackInterface).invoke(callback, listener);
dispatcher.getClass().getMethod("addCallback", Activity.class, callbackClass).invoke(dispatcher, this, callback);
} catch (Exception e) {
// 如果反射调用失败,不影响原有功能
Log.d(TAG, "OnBackPressedDispatcher not available, using legacy onBackPressed");
}
}
/**
@ -301,7 +245,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 初始当前文件夹为根目录
mNotesListView = (ListView) findViewById(R.id.notes_list); // 查找列表视图
mSmartReminderManager = new SmartReminderManager(this); // 初始化智能提醒管理器
// 为列表添加脚部视图(可能用于显示空白区域或加载更多)
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
@ -320,38 +263,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mDispatchY = 0;
mOriginY = 0;
mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 标题栏
// 初始化搜索相关UI元素
mSearchEditText = (EditText) findViewById(R.id.search_edit_text);
mSearchClearButton = (ImageButton) findViewById(R.id.search_clear_button);
// 初始化标签筛选按钮
Button tagFilterButton = (Button) findViewById(R.id.btn_tag_filter);
tagFilterButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showTagFilterDialog();
}
});
// 设置搜索输入框监听器
mSearchEditText.addTextChangedListener(new SearchTextWatcher());
mSearchEditText.setOnKeyListener(new SearchOnKeyListener());
// 设置搜索清除按钮监听器
mSearchClearButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mSearchEditText.setText("");
clearSearch();
}
});
mState = ListEditState.NOTE_LIST; // 初始状态为根目录笔记列表
mModeCallBack = new ModeCallback(); // 创建多选模式回调实例
// 初始化手势检测器,支持滑动操作
initSwipeGestureDetector();
}
/**
@ -587,173 +500,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
// 根据当前文件夹ID选择查询条件
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 设置查询参数
String[] selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
// 如果有搜索关键词,添加搜索条件
if (!TextUtils.isEmpty(mSearchQuery)) {
selection = "(" + selection + ") AND (" + NoteColumns.SNIPPET + " LIKE ?)";
selectionArgs = new String[] {
selectionArgs[0],
"%" + mSearchQuery + "%"
};
}
// 如果有标签筛选,添加标签条件
if (!TextUtils.isEmpty(mCurrentTagFilter)) {
String tagSelection = NoteColumns.TAG + "=?";
// 确保标签筛选条件被正确应用到整个查询条件中
selection = "(" + selection + ") AND " + tagSelection;
String[] newArgs = new String[selectionArgs.length + 1];
System.arraycopy(selectionArgs, 0, newArgs, 0, selectionArgs.length);
newArgs[selectionArgs.length] = mCurrentTagFilter;
selectionArgs = newArgs;
}
// 执行异步查询
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); // 排序:类型降序,修改日期降序
}
/**
*
*/
private class SearchTextWatcher implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
mSearchQuery = s.toString().trim();
mSearchClearButton.setVisibility(TextUtils.isEmpty(mSearchQuery) ? View.GONE : View.VISIBLE);
startAsyncNotesListQuery();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
}
/**
*
*/
private class SearchOnKeyListener implements OnKeyListener {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
// 隐藏软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
return true;
}
return false;
}
}
/**
*
*/
private void clearSearch() {
mSearchQuery = "";
mSearchClearButton.setVisibility(View.GONE);
startAsyncNotesListQuery();
}
/**
*
*/
private void showTagFilterDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择标签");
// 查询数据库获取所有使用过的标签
List<String> tagList = new ArrayList<>();
// 预设标签
List<String> defaultTags = new ArrayList<>();
defaultTags.add(NoteEditActivity.TAG_LIFE);
defaultTags.add(NoteEditActivity.TAG_STUDY);
defaultTags.add(NoteEditActivity.TAG_WORK);
// 自定义标签
List<String> customTags = new ArrayList<>();
// 查询所有标签
Cursor cursor = getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{Notes.NoteColumns.TAG},
Notes.NoteColumns.TAG + " IS NOT NULL AND " + Notes.NoteColumns.TAG + " != ''",
null,
null);
if (cursor != null) {
while (cursor.moveToNext()) {
String tag = cursor.getString(0);
if (tag != null && !tag.isEmpty()) {
tag = tag.trim();
if (defaultTags.contains(tag)) {
// 预设标签,不重复添加
continue;
} else if (NoteEditActivity.TAG_CUSTOM.equals(tag)) {
// 旧版自定义标签,跳过
continue;
} else {
// 自定义标签,添加到列表
if (!customTags.contains(tag)) {
customTags.add(tag);
}
}
}
}
cursor.close();
}
// 排序自定义标签
Collections.sort(customTags);
// 构建最终标签列表
List<String> finalTags = new ArrayList<>();
List<String> finalTagValues = new ArrayList<>();
// 添加"全部"选项
finalTags.add("全部");
finalTagValues.add("");
// 添加预设标签
finalTags.add("生活");
finalTagValues.add(NoteEditActivity.TAG_LIFE);
finalTags.add("学习");
finalTagValues.add(NoteEditActivity.TAG_STUDY);
finalTags.add("工作");
finalTagValues.add(NoteEditActivity.TAG_WORK);
// 添加自定义标签
for (String customTag : customTags) {
finalTags.add(customTag);
finalTagValues.add(customTag);
}
// 转换为数组
final String[] tags = finalTags.toArray(new String[0]);
final String[] tagValues = finalTagValues.toArray(new String[0]);
builder.setItems(tags, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 全部
mCurrentTagFilter = "";
((Button) findViewById(R.id.btn_tag_filter)).setText("全部");
} else {
// 选择标签
mCurrentTagFilter = tagValues[which];
((Button) findViewById(R.id.btn_tag_filter)).setText(tags[which]);
}
startAsyncNotesListQuery();
}
});
builder.show();
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId) // 参数当前文件夹ID
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); // 排序:类型降序,修改日期降序
}
/**
@ -848,7 +599,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} else {
// 同步模式:将笔记移动到垃圾箱文件夹(而不是直接删除)
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) {
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
@ -892,7 +643,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// 同步模式:将文件夹移动到垃圾箱
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLDER);
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// 更新关联的小部件
if (widgets != null) {
@ -1069,175 +820,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
});
}
/**
*
*/
private void initSwipeGestureDetector() {
mGestureDetector = new GestureDetector(this, new SwipeGestureListener());
// 为ListView设置触摸监听器将触摸事件传递给手势检测器
mNotesListView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 将触摸事件传递给手势检测器
return mGestureDetector.onTouchEvent(event);
}
});
}
/**
*
*/
private class SwipeGestureListener extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
// 检查是否是水平滑动且速度足够
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {
return false; // 垂直方向滑动过大,忽略
}
// 获取滑动位置对应的项索引
int position = mNotesListView.pointToPosition((int) e1.getX(), (int) e1.getY());
if (position < 0 || position >= mNotesListAdapter.getCount()) {
return false; // 位置无效
}
// 获取当前项的数据
Cursor cursor = (Cursor) mNotesListAdapter.getItem(position);
if (cursor == null) {
return false;
}
NoteItemData noteData = new NoteItemData(NotesListActivity.this, cursor);
// 左滑:删除笔记
if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
showDeleteConfirmation(noteData, position);
return true;
}
// 右滑:标记完成(仅对清单模式有效)
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
toggleNoteCompletion(noteData);
return true;
}
} catch (Exception e) {
Log.e(TAG, "Swipe gesture error: " + e.getMessage());
}
return false;
}
@Override
public boolean onDown(MotionEvent e) {
return true; // 必须返回true否则不会触发后续的手势事件
}
}
/**
*
*/
private void showDeleteConfirmation(final NoteItemData noteData, final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(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_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteNote(noteData);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
/**
*
*/
private void deleteNote(NoteItemData noteData) {
HashSet<Long> ids = new HashSet<Long>();
ids.add(noteData.getId());
if (!isSyncMode()) {
// 非同步模式:直接删除笔记
if (DataUtils.batchDeleteNotes(mContentResolver, ids)) {
Toast.makeText(this, "笔记已删除", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Delete note error");
}
} else {
// 同步模式:将笔记移动到垃圾箱文件夹
if (DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLDER)) {
Toast.makeText(this, "笔记已移至垃圾箱", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Move note to trash error");
}
}
// 更新关联的小部件
if (noteData.getWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID &&
noteData.getWidgetType() != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(noteData.getWidgetId(), noteData.getWidgetType());
}
// 刷新列表
startAsyncNotesListQuery();
}
/**
* 便
* @param noteData 便
*/
private void toggleNoteCompletion(NoteItemData noteData) {
try {
// 加载便签内容
WorkingNote workingNote = WorkingNote.load(this, noteData.getId());
if (workingNote == null) {
Toast.makeText(this, "无法加载便签", Toast.LENGTH_SHORT).show();
return;
}
String content = workingNote.getContent();
if (TextUtils.isEmpty(content)) {
Toast.makeText(this, "便签内容为空", Toast.LENGTH_SHORT).show();
return;
}
// 检查当前便签是否已经标记为完成
if (content.startsWith(NoteEditActivity.TAG_CHECKED)) {
// 已完成状态,切换为未完成
content = content.replaceFirst(NoteEditActivity.TAG_CHECKED, NoteEditActivity.TAG_UNCHECKED);
Toast.makeText(this, "已标记为未完成", Toast.LENGTH_SHORT).show();
} else if (content.startsWith(NoteEditActivity.TAG_UNCHECKED)) {
// 未完成状态,切换为已完成
content = content.replaceFirst(NoteEditActivity.TAG_UNCHECKED, NoteEditActivity.TAG_CHECKED);
Toast.makeText(this, "已标记为完成", Toast.LENGTH_SHORT).show();
} else {
// 没有状态标记,默认标记为已完成
content = NoteEditActivity.TAG_CHECKED + content;
Toast.makeText(this, "已标记为完成", Toast.LENGTH_SHORT).show();
}
// 更新便签内容
workingNote.setWorkingText(content);
if (workingNote.saveNote()) {
// 刷新列表
startAsyncNotesListQuery();
// 更新关联的小部件
if (noteData.getWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID &&
noteData.getWidgetType() != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(noteData.getWidgetId(), noteData.getWidgetType());
}
} else {
Toast.makeText(this, "保存便签失败", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Log.e(TAG, "Toggle completion error: " + e.getMessage());
Toast.makeText(this, "操作失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
@ -1267,7 +849,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
break;
}
}
/**
*
* @param appWidgetId ID
@ -1328,37 +910,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Log.e(TAG, "The long click data item is null");
return false;
}
// 处理便签的上下文菜单
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE) {
switch (item.getItemId()) {
case R.id.edit:
editNote(mFocusNoteDataItem.getId());
return true;
case R.id.share:
shareNote(mFocusNoteDataItem.getId());
return true;
case R.id.copy:
copyNote(mFocusNoteDataItem.getId());
return true;
case R.id.move:
// 原有的移动逻辑
break;
case R.id.add_lock:
showAddLockDialog(mFocusNoteDataItem.getId());
return true;
case R.id.remove_lock:
showRemoveLockDialog(mFocusNoteDataItem.getId());
return true;
case R.id.delete:
// 原有的删除逻辑
break;
default:
return super.onContextItemSelected(item);
}
}
// 处理文件夹的上下文菜单
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem); // 打开文件夹
@ -1414,7 +965,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d(TAG, "onOptionsItemSelected called: " + item.getItemId());
switch (item.getItemId()) {
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true); // 创建新文件夹
@ -1529,16 +1079,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
/**
*
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.note_list, menu);
return true;
}
/**
*
*
*/
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@ -1605,7 +1146,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER), // 类型:文件夹
String.valueOf(Notes.ID_TRASH_FOLDER), // 排除垃圾箱
String.valueOf(Notes.ID_TRASH_FOLER), // 排除垃圾箱
String.valueOf(mCurrentFolderId) // 排除当前文件夹
},
NoteColumns.MODIFIED_DATE + " DESC"); // 按修改日期降序排序
@ -1634,187 +1175,4 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
return false; // 返回false让系统继续处理长按事件显示上下文菜单等
}
/**
* 便
*/
private void editNote(long noteId) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.putExtra(NotesListActivity.NOTE_ID, noteId);
startActivity(intent);
}
/**
* 便
*/
private void shareNote(long noteId) {
// 获取便签内容
Cursor cursor = getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[]{Notes.NoteColumns.SNIPPET},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String content = cursor.getString(cursor.getColumnIndexOrThrow(Notes.NoteColumns.SNIPPET));
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "便签");
shareIntent.putExtra(Intent.EXTRA_TEXT, content);
startActivity(Intent.createChooser(shareIntent, "分享便签"));
cursor.close();
}
}
/**
* 便
*/
private void copyNote(long noteId) {
// 获取原便签内容
Cursor cursor = getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[]{Notes.NoteColumns.SNIPPET},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String content = cursor.getString(cursor.getColumnIndexOrThrow(Notes.NoteColumns.SNIPPET));
// 创建新便签,复制内容
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.SNIPPET, content + " (复制)");
values.put(Notes.NoteColumns.CREATED_DATE, System.currentTimeMillis());
values.put(Notes.NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
values.put(Notes.NoteColumns.PARENT_ID, mCurrentFolderId);
values.put(Notes.NoteColumns.TYPE, Notes.TYPE_NOTE);
// 插入新便签
Uri newNoteUri = getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
if (newNoteUri != null) {
Toast.makeText(this, "复制成功", Toast.LENGTH_SHORT).show();
// 刷新列表
refresh();
}
cursor.close();
}
}
/**
*
*/
private void showAddLockDialog(final long noteId) {
// 显示锁类型选择对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择锁类型");
builder.setItems(new String[]{"密码锁", "手势锁"}, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 密码锁
showPasswordInputDialog(noteId, true);
} else {
// 手势锁
showGestureInputDialog(noteId, true);
}
}
});
builder.show();
}
/**
*
*/
private void showPasswordInputDialog(final long noteId, final boolean isAddLock) {
final EditText passwordEditText = new EditText(this);
passwordEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordEditText.setHint("请输入密码");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(isAddLock ? "设置密码" : "验证密码");
builder.setView(passwordEditText);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString().trim();
if (!TextUtils.isEmpty(password)) {
if (isAddLock) {
// 添加密码锁
PrivacyLockManager lockManager = new PrivacyLockManager(NotesListActivity.this);
String encryptedPassword = PrivacyLockManager.encryptPassword(password);
boolean success = lockManager.addPrivacyLock(noteId, PrivacyLockManager.LOCK_TYPE_PASSWORD, encryptedPassword);
if (success) {
Toast.makeText(NotesListActivity.this, "密码锁添加成功", Toast.LENGTH_SHORT).show();
refresh();
} else {
Toast.makeText(NotesListActivity.this, "密码锁添加失败", Toast.LENGTH_SHORT).show();
}
} else {
// 验证密码
PrivacyLockManager lockManager = new PrivacyLockManager(NotesListActivity.this);
boolean isValid = lockManager.verifyPassword(noteId, password);
if (isValid) {
// 密码正确,移除锁
boolean success = lockManager.removePrivacyLock(noteId);
if (success) {
Toast.makeText(NotesListActivity.this, "隐私锁已移除", Toast.LENGTH_SHORT).show();
refresh();
} else {
Toast.makeText(NotesListActivity.this, "隐私锁移除失败", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(NotesListActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
}
}
} else {
Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
/**
*
*/
private void showGestureInputDialog(final long noteId, final boolean isAddLock) {
// 这里需要实现手势输入对话框
// 由于需要使用GestureLockView这里简化处理实际项目中需要创建包含GestureLockView的对话框
Toast.makeText(this, "手势锁功能需要在实际设备上测试", Toast.LENGTH_SHORT).show();
}
/**
*
*/
private void showRemoveLockDialog(final long noteId) {
PrivacyLockManager lockManager = new PrivacyLockManager(this);
int lockType = lockManager.getLockType(noteId);
if (lockType == PrivacyLockManager.LOCK_TYPE_PASSWORD) {
showPasswordInputDialog(noteId, false);
} else if (lockType == PrivacyLockManager.LOCK_TYPE_GESTURE) {
showGestureInputDialog(noteId, false);
}
}
/**
* 便
*/
private void refresh() {
// 重新查询数据
if (mBackgroundQueryHandler != null) {
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI,
null,
Notes.NoteColumns.PARENT_ID + "=? AND " + Notes.NoteColumns.TYPE + "!=?",
new String[]{
String.valueOf(mCurrentFolderId),
String.valueOf(Notes.TYPE_SYSTEM)
},
Notes.NoteColumns.MODIFIED_DATE + " DESC");
}
}
}

@ -17,7 +17,6 @@
package net.micode.notes.ui;
import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
@ -39,7 +38,6 @@ public class NotesListItem extends LinearLayout {
private TextView mTitle; // 标题/内容文本
private TextView mTime; // 修改时间文本
private TextView mCallName; // 通话记录联系人姓名
private TextView mTag; // 标签文本
private NoteItemData mItemData; // 绑定的数据对象
private CheckBox mCheckBox; // 多选模式下的复选框
@ -56,7 +54,6 @@ public class NotesListItem extends LinearLayout {
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mTag = (TextView) findViewById(R.id.tv_tag);
// 使用系统预定义的checkbox ID
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
@ -117,26 +114,12 @@ public class NotesListItem extends LinearLayout {
data.getNotesCount()));
mAlert.setVisibility(View.GONE); // 文件夹不显示提醒图标
} else {
// 普通笔记显示:标题
String title = data.getTitle();
String content = data.getContentSnippet();
if (TextUtils.isEmpty(title)) {
// 如果没有标题,显示内容摘要
mTitle.setText(DataUtils.getFormattedSnippet(content));
} else {
// 如果有标题,显示标题
mTitle.setText(title);
}
// 普通笔记显示:内容摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 根据是否有提醒设置提醒图标
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); // 时钟图标
mAlert.setVisibility(View.VISIBLE);
} else if (data.isCompleted()) {
// 如果是已完成的便签,显示已完成图标(使用系统内置图标)
mAlert.setImageResource(android.R.drawable.checkbox_on_background); // 已完成图标
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
@ -146,26 +129,6 @@ public class NotesListItem extends LinearLayout {
// 设置相对时间显示(如"2分钟前"、"昨天"等)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置标签显示
String tag = data.getTag();
if (tag != null && !tag.trim().isEmpty()) {
String tagText = "";
tag = tag.trim(); // 去除前后空格
if (NoteEditActivity.TAG_LIFE.equals(tag)) {
tagText = "生活";
} else if (NoteEditActivity.TAG_STUDY.equals(tag)) {
tagText = "学习";
} else if (NoteEditActivity.TAG_WORK.equals(tag)) {
tagText = "工作";
} else {
tagText = tag; // 直接显示标签值,包括自定义标签
}
mTag.setText("标签:" + tagText);
mTag.setVisibility(View.VISIBLE);
} else {
mTag.setVisibility(View.GONE);
}
// 根据位置和类型设置背景
setBackground(data);
}

@ -1,277 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.os.Build;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.widget.EditText;
import android.graphics.Typeface;
import android.text.Spannable;
public class RichEditor extends EditText {
public interface OnTextChangeListener {
void onTextChange(String text);
}
private OnTextChangeListener onTextChangeListener;
public RichEditor(Context context) {
super(context);
init();
}
public RichEditor(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 初始化逻辑
}
public void setOnTextChangeListener(OnTextChangeListener listener) {
this.onTextChangeListener = listener;
// 监听文本变化
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (onTextChangeListener != null) {
onTextChangeListener.onTextChange(s.toString());
}
}
@Override
public void afterTextChanged(Editable s) {}
});
}
// 重写获取文本的方法
@Override
public Editable getText() {
return super.getText();
}
// 重写设置文本的方法,包括设置文本颜色、文本样式等
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
}
// 重写设置光标位置的方法
@Override
public void setSelection(int index) {
super.setSelection(index);
}
// 重写设置选中文本的方法
@Override
public void setSelection(int start, int stop) {
super.setSelection(start, stop);
}
// 重写获取选中文本起始位置的方法
@Override
public int getSelectionStart() {
return super.getSelectionStart();
}
// 重写获取选中文本结束位置的方法
@Override
public int getSelectionEnd() {
return super.getSelectionEnd();
}
// 重写设置文本外观的方法,文本外观包括字体、颜色等
@Override
public void setTextAppearance(Context context, int resid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
super.setTextAppearance(resid);
} else {
// 对旧版本的兼容处理
setTextAppearance(context, resid);
}
}
// 设置编辑器高度
public void setEditorHeight(int pixels) {
this.setHeight(pixels);
}
//设置编辑器字体大小
public void setEditorFontSize(int size) {
this.setTextSize(size);
}
//设置编辑器字体颜色
public void setEditorFontColor(int color) {
this.setTextColor(color);
}
//设置占位符文本
public void setPlaceholder(String placeholder) {
this.setHint(placeholder);
}
// 设置编辑器启用或禁用输入
public void setInputEnabled(boolean enabled) {
this.setEnabled(enabled);
}
//切换粗体样式
public void toggleBold() {
toggleStyleSpan(Typeface.BOLD);
}
//切换斜体样式
public void toggleItalic() {
toggleStyleSpan(Typeface.ITALIC);
}
//切换删除线样式
public void toggleStrikeThrough() {
toggleSpan(StrikethroughSpan.class, new StrikethroughSpan());
}
//切换下划线样式
public void toggleUnderline() {
toggleSpan(UnderlineSpan.class, new UnderlineSpan());
}
// 粗体、斜体样式切换方法
private void toggleStyleSpan(int style) {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start < 0 || end < 0 || start >= end) {
return; // 没有选中文本时不处理
}
Spannable str = getText();
StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
boolean hasStyle = false;
// 检查是否已存在相同样式
for (StyleSpan span : spans) {
if (span.getStyle() == style) {
str.removeSpan(span);
hasStyle = true;
}
}
// 如果不存在对应样式,则添加
if (!hasStyle) {
str.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
//通用的Span切换方法下划线和删除线
private <T> void toggleSpan(Class<T> spanType, T newSpan) {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start < 0 || end < 0 || start >= end) {
return;
}
Spannable str = getText();
@SuppressWarnings("unchecked")
T[] spans = (T[]) str.getSpans(start, end, spanType);
if (spans.length > 0) {
// 如果已存在样式,则移除
for (T span : spans) {
str.removeSpan(span);
}
} else {
// 如果不存在样式,则添加
str.setSpan(newSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 应用粗体样式
public void applyBold() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start >= 0 && end >= 0 && start != end) {
Spannable str = getText();
StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
for (StyleSpan span : spans) {
if (span.getStyle() == Typeface.BOLD) {
str.removeSpan(span);
}
}
str.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 应用斜体样式
public void applyItalic() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start >= 0 && end >= 0 && start != end) {
Spannable str = getText();
StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
for (StyleSpan span : spans) {
if (span.getStyle() == Typeface.ITALIC) {
str.removeSpan(span);
}
}
str.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 应用删除线样式
public void applyStrikeThrough() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start >= 0 && end >= 0 && start != end) {
Spannable str = getText();
StrikethroughSpan[] spans = str.getSpans(start, end, StrikethroughSpan.class);
for (StrikethroughSpan span : spans) {
str.removeSpan(span);
}
str.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 应用下划线样式
public void applyUnderline() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start >= 0 && end >= 0 && start != end) {
Spannable str = getText();
UnderlineSpan[] spans = str.getSpans(start, end, UnderlineSpan.class);
for (UnderlineSpan span : spans) {
str.removeSpan(span);
}
str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}

@ -1,65 +0,0 @@
package net.micode.notes.ui;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import net.micode.notes.R;
public class SplashActivity extends AppCompatActivity {
private static final int SPLASH_DURATION = 3000; // 3秒
private static final int TEXT_FADE_IN_DELAY = 2000; // 2秒后文字淡入
// 启动页创建,初始化动画界面和跳转逻辑
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
ImageView logo = findViewById(R.id.splash_logo);
TextView text = findViewById(R.id.splash_text);
// 加载文字滑动动画
Animation slideUpAnimation = AnimationUtils.loadAnimation(this, R.anim.text_slide_up);
// 2秒后显示文字动画
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
text.setVisibility(android.view.View.VISIBLE);
text.startAnimation(slideUpAnimation);
}
}, TEXT_FADE_IN_DELAY);
// 3秒后跳转到主界面
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
onSplashComplete();
}
}, SPLASH_DURATION);
}
// 动画完成回调方法
private void onSplashComplete() {
Intent intent = new Intent(SplashActivity.this, LoginRegisterActivity.class);
startActivity(intent);
finish(); // 结束启动页,防止用户返回到此页面
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
// 处理屏幕旋转等配置变更
@Override
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 重新启动启动画面,以正确处理方向更改
recreate();
}
}

@ -1,76 +0,0 @@
package net.micode.notes.ui;
import android.animation.ValueAnimator;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
public class WaveAnimation {
// 整行文字从左到右淡入显示动画
public static void applyFadeInAnimation(TextView textView) {
// 设置初始状态为完全透明
textView.setAlpha(0f);
textView.setVisibility(android.view.View.VISIBLE);
// 创建透明度渐变动画
ValueAnimator fadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
fadeInAnimator.setDuration(2000); // 2秒淡入效果
fadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
fadeInAnimator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
textView.setAlpha(alpha);
});
// 创建从左到右的滑入效果
ValueAnimator slideInAnimator = ValueAnimator.ofFloat(-100f, 0f);
slideInAnimator.setDuration(1500); // 1.5秒滑入
slideInAnimator.setInterpolator(new android.view.animation.OvershootInterpolator());
slideInAnimator.addUpdateListener(animation -> {
float translationX = (float) animation.getAnimatedValue();
textView.setTranslationX(translationX);
});
// 同时启动两个动画
fadeInAnimator.start();
slideInAnimator.start();
}
// 从左到右逐字淡入效果(如果需要的话)
public static void applyCharByCharFadeIn(TextView textView) {
String text = textView.getText().toString();
int length = text.length();
// 设置初始状态
textView.setAlpha(0f);
textView.setVisibility(android.view.View.VISIBLE);
// 为整行文字创建统一的淡入动画
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(2500); // 总时长2.5秒
animator.setInterpolator(new android.view.animation.DecelerateInterpolator());
animator.addUpdateListener(animation -> {
float progress = (float) animation.getAnimatedValue();
textView.setAlpha(progress);
// 添加轻微的缩放效果
float scale = 0.8f + (progress * 0.2f); // 从0.8倍放大到1倍
textView.setScaleX(scale);
textView.setScaleY(scale);
});
animator.start();
}
// 兼容旧版本的方法名 - 使用新的淡入效果
public static void applyComplexWaveAnimation(TextView textView) {
applyFadeInAnimation(textView);
}
// 兼容旧版本的另一个方法名
public static void applyCharWaveAnimation(TextView textView) {
applyCharByCharFadeIn(textView);
}
}

@ -94,7 +94,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION, // 查询的列
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 查询条件
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLDER) },
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null); // 排序方式(无)
}

Loading…
Cancel
Save