代码合并

mengcheng_branch
mc19 2 months ago
parent 22720f6f47
commit d4024054a6

@ -0,0 +1,24 @@
package net.micode.notes;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

@ -32,10 +32,15 @@ import android.util.Log;
import java.util.HashMap;
/**
* Contact
*
*
*/
*Contact
*
*<P></P>
*
*
* @author
* @version 1.0
* @since []
*/
public class Contact {
//HashMap表示联系人和电话号码之间的映射关系。只声明不new是延迟初始化的表现
private static HashMap<String, String> sContactCache;
@ -51,13 +56,15 @@ public class Contact {
/**
*
*
* <P></P>
* @param context 访ContentResolver
* @param phoneNumber 0
* @return (code null)
* @return code null
* @throws
* @see android.provider.ContactsContract.CommonDataKinds
*/
public static String getContact(Context context, String phoneNumber) {
//不到真正要用的时候,不给对象分配内存第一次被调用时才new
//不到真正要用的时候,不给对象分配内存。第一次被调用时才new。
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
@ -67,11 +74,11 @@ public class Contact {
return sContactCache.get(phoneNumber);
}
//构造匹配串,提取电话号码数字核心部分
//构造匹配串,提取数字核心部分
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
//getcontentresolver.query()方法查询数据库,根据URI和电话号码匹配
//cursor是数据库游标初始位置是-1query把SQL拼装好后交给系统联系人Provider
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
@ -79,17 +86,16 @@ public class Contact {
new String[] { phoneNumber },
null);
//利用cursor获取联系人姓名
//指针移到第一行成功返回true
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name); //将号码-姓名键值对放进内存缓存
return name;
} catch (IndexOutOfBoundsException e) {
//捕获索引越界异常
} catch (IndexOutOfBoundsException e) { //捕获异常,索引超出列范围的情况,返回错误日志
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
} finally { //无论是否发生异常都必须释放cursor。
cursor.close();
}
} else { //查询失败,输出日志信息

@ -25,18 +25,21 @@ package net.micode.notes.data;
import android.net.Uri;
/**
* Notes便
* <p>URI便/Intent</p>
* static final,
*Notes便
* <P>URI便/Intent</P>
* <b>static final</b>
* @author
* @since []
*/
public class Notes {
//一、ContentProvider权威域名
public static final String AUTHORITY = "micode_notes"; //拼接URI的前缀
public static final String TAG = "Notes"; //日志显示的标记
public static final String TAG = "Notes"; //日志显示Notes
/**
* 便//
*/
//二、便签/文件夹/系统文件夹 类型常量
public static final int TYPE_NOTE = 0; //普通文本便签类型常量为0
public static final int TYPE_FOLDER = 1; //文件夹类型常量为1
public static final int TYPE_SYSTEM = 2; //系统文件夹如回收站类型常量为2
@ -47,15 +50,15 @@ public class Notes {
* {@link Notes#ID_TEMPORARY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
//三、系统保留文件夹 ID
public static final int ID_ROOT_FOLDER = 0; //默认文件夹ID为0
public static final int ID_TEMPORARY_FOLDER = -1; //临时文件夹
public static final int ID_CALL_RECORD_FOLDER = -2; //通话记录文件夹ID保存由通话备忘功能生成的便签
public static final int ID_TRASH_FOLDER = -3;
/**
* Intent
*/
//四、Intent是什么?
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; //提醒时间戳
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; //便签背景颜色
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; //桌面便签小部件
@ -63,23 +66,21 @@ public class Notes {
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; //便签父文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; //通话记录时间戳
/**
*
*/
//五、桌面小部件类型常量
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
/**
* MIME
* AndroidContentResolver//MIME
*便MIME
* Android ContentResolver // URI MIME
*/
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; //文本便签MIME类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; //通话备忘MIME类型
public static final String IMAGE = ImageData.CONTENT_ITEM_TYPE; //图片MIME类型
}
//七、基础URI
/**
* Uri to query all notes and folders
*/
@ -91,9 +92,10 @@ public class Notes {
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
//八、数据库列名接口 —— Note 表
/**
* NoteColumns便
* 便便IDID,
* NoteColumns便/
* 便ID,PARENT_ID,
*/
public interface NoteColumns {
/**
@ -193,31 +195,36 @@ 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
@ -245,8 +252,10 @@ public class Notes {
}
//九、数据库列名接口 —— Data 表
/**
* 便
* DataColumns
*/
public interface DataColumns {
/**
@ -320,51 +329,37 @@ public class Notes {
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
/**
* Rich text format information for storing span information
* <P> Type: TEXT </P>
*/
public static final String RICH_TEXT_FORMAT = "rich_text_format";
/**
* Path to the image file for image data
* <P> Type: TEXT </P>
*/
public static final String IMAGE_PATH = "image_path";
}
/**
* TextNote便
* DataColumns
*/
//十、文本便签实体常量类
//文本便签TextNote类实现DataColumns接口中声明的方法
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
public static final String MODE = DATA1; //存储文本的清单模式或普通文本模式
public static final String MODE = DATA1; //清单模式or普通文本模式
public static final int MODE_CHECK_LIST = 1;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; //MIME类型为批量笔记
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; //MIME类型为集合(多条)
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; //MIME类型为单条笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; //MIME类型为单条
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
/**
* CallNote便
* TextNoteCalloteDataColumns
*便
* CallNoteDataColumns
*/
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1; //DATA1存储通话的时间戳
public static final String CALL_DATE = DATA1; //DATA1是数据库中的真实列名将通话的时间戳存入DATA1列中
/**
* Phone number for this record
@ -378,22 +373,4 @@ public class Notes {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
/**
*
* 便
*/
public static final class ImageData implements DataColumns {
/**
* Path to the image file
* <P> Type: TEXT </P>
*/
public static final String PATH = DATA3;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_data";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_data";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/image_data");
}
}

@ -32,29 +32,31 @@ import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
*便
*便
<P>
*
*<ul>
* <li>7</li>
* <li>note.db</li>
* <li>/notedata</li>
* <li>便</li>
* <li></li>
* <li></li>
* <li>/ note data </li>
* <li>便便</li>
* <li></li>
* <li></li>
*</ul>
</P>
*
* @author
* @version 1.0
* @since 2025-12-13
* @version {@link #DB_VERSION} 4
* @since 2010-2011
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
*
*
*/
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 7;
private static final int DB_VERSION = 8;
/**表明常量接口*/
public interface TABLE {
public static final String NOTE = "note";
@ -63,17 +65,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "NotesDatabaseHelper";
//类加载时不创建实例单例,真正需要使用时才调用getInstance初始化节省应用启动时的内存和资源开销。
//类加载时不创建NotesDatabaseHelper实例(mInstance),真正需要使用数据库时才调用getInstance初始化节省应用启动时的内存和资源开销。
private static NotesDatabaseHelper mInstance;
//二、建表SQL初始化note和data表同时添加note_id_index索引列
/**
* NoteData
* NoteColumns Notes便IDPARENT_ID
* <p>
* NOT NULLNULLDEFAULT 0 0,
* NOT NULLDEFAULT
* </p>
* strftime SQLite%sUnixnow'
* NotesNoteColumns
* NotesNoteColumns便IDPARENT_IDNotes
* NOT NULLNULLDEFAULT 0 0NOT NULLDEFAULT
* strftimeSQLite%sUnixnow'
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + //开始建表表名是TABLE.NOTE(note)
@ -93,18 +94,22 @@ 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.VERSION + " INTEGER NOT NULL DEFAULT 0" +
NoteColumns.LOCKED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ENCRYPTED_PASSWORD + " TEXT NOT NULL DEFAULT ''" +
")";
/**
* Data SQLNotesDataColumns
* <p>
* {@link DataColumns#NOTE_ID} note.id <br>
* data1~data5 {@link DataColumns#MIME_TYPE}
* {@link DataColumns#NOTE_ID} note.id <br>
* data1~data5 {@link DataColumns#MIME_TYPE}
* </p>
*/
private static final String CREATE_DATA_TABLE_SQL =
@ -119,25 +124,24 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.RICH_TEXT_FORMAT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.IMAGE_PATH + " TEXT NOT NULL DEFAULT ''" +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
/**
* <p>data note_id</p>
* <p>便"SELECT * FROM data WHERE note_id=?"</p>
*data(NOTE_ID)note_id_index
* 便SELECT * FROM data WHERE note_id=?
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
//三、创建Note表触发器在文件夹下便签数量变化时自动增减便签数量(notes_count)的值
/**
* Note便便
* Increase folder's note count when move note to the folder
* increase_folder_count_on_update
* note parent_id
* Note notes_count=notes_count+1, id=new.parent_id
* increase_folder_count_on_update
* noteparent_id
* Notenotes_count=notes_count+1,id=new.parent_id
* 便
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
@ -187,11 +191,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
//四、Data 表触发器自动同步便签摘要snippet)
/**
* Data 便snippet
* Update note's content when insert data with type {@link DataConstants#NOTE}
* mime_typenote
* note_id Notesnippet DataColumncontent
* DATAmime_typenote()
* notesnippetcontentnoteid=note_id
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
@ -231,8 +235,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Delete data belong to note which has been deleted
* note noteid data note_id
* data
* notedatanote_idnoteid
* data-
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
@ -244,8 +248,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Delete notes belong to folder which has been deleted
* notenoteparent_idid
*
* notenoteparent_idid
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
@ -269,7 +272,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Android/
* Android/
* @param context 访
*/
public NotesDatabaseHelper(Context context) {
@ -277,15 +280,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/** super(SQLiteOpenHelper)
* @param context context
* @param DB_NAME
* @param null (cursor),null( 1)
* @param DB_VERSION
* @param null (cursor),null
* @param DB_VERSION
*/
super(context, DB_NAME, null, DB_VERSION);
}
/**
* Note
*/
//完成note表的全量初始化
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); //执行SQL语句创建note表
reCreateNoteTableTriggers(db); //重建该表关联的触发器
@ -293,9 +294,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
Log.d(TAG, "note table has been created"); //打印日志确认note表成功创建
}
/**
*
*/
//批量重建触发器,先删除旧的,再建立新的
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
@ -314,11 +313,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
*
* note.
* @param db
*/
//向note表插入四个系统默认文件夹通话记录、根、临时、回收站。保证应用首次启动时有基础的文件夹结构。
private void createSystemFolder(SQLiteDatabase db) {
//ContentValues类用于存储键值对
ContentValues values = new ContentValues();
@ -355,9 +350,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.insert(TABLE.NOTE, null, values);
}
/**
* Data
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
@ -376,9 +368,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
}
/**
* synchronized线线
* getInstance
* synchronized线线if
* getInstance
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
//首次调用该方法时,才创建实例;若已创建,则直接复用
if (mInstance == null) {
@ -387,24 +380,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
return mInstance;
}
/**
* onCreate
* "@override"
*/
//@override是Java中的一种注解用于明确表示子类的方法是对父类方法的重写实现多态性
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); //初始化笔记表Note
createDataTable(db); //初始化数据表Data
createNoteTable(db); //初始化笔记表
createDataTable(db); //初始化数据表
}
/**
* onUpgrade
* <p>
*
* <li> rich_text_format</li>
* <li>, </li>
* </p>
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
@ -428,24 +410,24 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
}
if (oldVersion == 4) {
upgradeToV5(db);
oldVersion++;
}
upgradeToV5(db);
oldVersion++;
}
if (oldVersion == 5) {
upgradeToV6(db);
oldVersion++;
}
upgradeToV6(db);
oldVersion++;
}
if (oldVersion == 6) {
upgradeToV7(db);
oldVersion++;
}
upgradeToV7(db);
oldVersion++;
}
if (oldVersion == 7) {
upgradeToV8(db);
oldVersion++;
}
upgradeToV8(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
@ -454,13 +436,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
+ " fails");
}
}
/**
* v1v2note data
*/
//v1到v2的升级删除旧的note data表清空所有用户数据重建新的
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); //执行具体的SQL语句删除旧的note表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); //删除旧的data表
@ -468,9 +448,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createDataTable(db);
}
/**
* v2v3Note gtask_id
*/
//v2到v3的升级清除无用触发器新增gtask_id列新增回收站文件夹
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
@ -486,49 +464,46 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.insert(TABLE.NOTE, null, values);
}
/**
*v3 v4 note version0
*/
//v3到v4的升级为note表新增version列且约束非空默认为0
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
/**
* v4v5 data rich_text_format
*/
//v4到v5的升级为note表新增tag列且约束非空默认为空字符串
private void upgradeToV5(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN rich_text_format TEXT NOT NULL DEFAULT ''");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TAG
+ " TEXT NOT NULL DEFAULT ''");
}
/**
* v5v6 data
*/
//v5到v6的升级为note表新增位置提醒相关的列
private void upgradeToV6(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN image_path TEXT NOT NULL DEFAULT ''");
// 添加位置提醒相关的列
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 ''");
}
/**
* v6v7 note
*/
//v6到v7的升级
private void upgradeToV7(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 版本中添加过了
}
/**
* v7v8 note
*/
//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 ''");
}
}
}

@ -108,8 +108,10 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean authenticated = cursor.getCount() > 0;
cursor.close();
return authenticated;
}
/**
*
* @param username
@ -126,6 +128,7 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}

@ -81,7 +81,7 @@ public class GTaskSyncService extends Service {
/**
*
* /
* @param intent The Intent supplied to {@link android.content.Context#startService},
* @param intent The Intent supplied to {@link 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}.

@ -110,15 +110,6 @@ public class Note {
mNoteData.setCallData(key, value);
}
public void setRichTextFormat(String formatInfo) {
mNoteData.setRichTextFormat(formatInfo);
}
// 设置图片数据
public void setImageData(String key, String value) {
mNoteData.setImageData(key, value);
}
//是否有本地修改
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
@ -166,10 +157,6 @@ public class Note {
private ContentValues mTextDataValues; // 存储文本数据的变化
private ContentValues mRichTextFormatValues; // 存储富文本格式信息
private ContentValues mImageDataValues; // 存储图片数据的变化
private long mCallDataId; // 通话记录数据在数据库中的ID0表示未创建
private ContentValues mCallDataValues; // 存储通话数据的变化(如号码更新)
@ -178,15 +165,13 @@ public class Note {
public NoteData() {
mTextDataValues = new ContentValues();
mRichTextFormatValues = new ContentValues();
mImageDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0 || mImageDataValues.size() > 0;
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
void setTextDataId(long id) {
@ -215,18 +200,6 @@ public class Note {
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setRichTextFormat(String formatInfo) {
mRichTextFormatValues.put(DataColumns.RICH_TEXT_FORMAT, formatInfo);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setImageData(String key, String value) {
mImageDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* /
* ID=0ID>0
@ -248,27 +221,22 @@ public class Note {
ContentProviderOperation.Builder builder = null;
//处理文本数据同步
if(mTextDataValues.size() > 0 || mRichTextFormatValues.size() > 0) {
if(mTextDataValues.size() > 0) {
// 关联文本数据到当前笔记
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
//合并文本数据和富文本格式信息
ContentValues combinedValues = new ContentValues(mTextDataValues);
combinedValues.putAll(mRichTextFormatValues);
//文本数据未创建
if (mTextDataId == 0) {
// 插入数据到ContentProvider获取返回的Uri
combinedValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
combinedValues);
mTextDataValues);
try {
// 从Uri解析新插入的文本数据ID并存储
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
mRichTextFormatValues.clear();
return null;
}
} else {
@ -276,42 +244,10 @@ public class Note {
// 构建更新操作指定数据ID对应的Uri
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(combinedValues); // 设置要更新的内容
builder.withValues(mTextDataValues); // 设置要更新的内容
operationList.add(builder.build()); // 添加到批量操作列表
}
mTextDataValues.clear();
mRichTextFormatValues.clear();
}
//处理图片数据同步
if(mImageDataValues.size() > 0) {
mImageDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) { // 使用文本数据ID作为参考因为图片通常与文本数据一起存储
mImageDataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.IMAGE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mImageDataValues);
try {
// 图片数据ID可能需要单独处理这里简化处理
Log.d(TAG, "Inserting image data");
} catch (Exception e) {
Log.e(TAG, "Insert new image data fail with noteId" + noteId);
mImageDataValues.clear();
return null;
}
} else {
// 如果文本数据已存在,我们需要为图片数据创建新的记录
mImageDataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.IMAGE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mImageDataValues);
try {
Log.d(TAG, "Inserting additional image data");
} catch (Exception e) {
Log.e(TAG, "Insert additional image data fail with noteId" + noteId);
mImageDataValues.clear();
return null;
}
}
mImageDataValues.clear();
}
//处理通话数据同步(逻辑同处理文本数据同步)

@ -28,7 +28,6 @@ import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
@ -56,6 +55,8 @@ public class WorkingNote {
private int mBgColorId;
private String mTag;
private int mWidgetId;
private int mWidgetType;
@ -69,7 +70,6 @@ public class WorkingNote {
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
private String mRichTextFormatInfo;
//指定查询Data表时需要返回的字段
public static final String[] DATA_PROJECTION = new String[] {
@ -80,9 +80,6 @@ public class WorkingNote {
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
DataColumns.DATA5,
DataColumns.RICH_TEXT_FORMAT,
DataColumns.IMAGE_PATH
};
//指定查询Note表时需要返回的字段
@ -96,7 +93,8 @@ public class WorkingNote {
NoteColumns.ALERT_LATITUDE,
NoteColumns.ALERT_LONGITUDE,
NoteColumns.ALERT_RADIUS,
NoteColumns.ALERT_LOCATION_NAME
NoteColumns.ALERT_LOCATION_NAME,
NoteColumns.TAG
};
private static final int DATA_ID_COLUMN = 0;
@ -107,10 +105,6 @@ public class WorkingNote {
private static final int DATA_MODE_COLUMN = 3;
private static final int DATA_RICH_TEXT_FORMAT_COLUMN = 8;
private static final int DATA_IMAGE_PATH_COLUMN = 9;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
@ -126,6 +120,7 @@ public class WorkingNote {
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) {
@ -172,14 +167,11 @@ 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);
// 加载富文本格式信息
mRichTextFormatInfo = cursor.getString(DATA_RICH_TEXT_FORMAT_COLUMN);
mTag = cursor.getString(NOTE_TAG_COLUMN);
}
cursor.close();
} else {
@ -206,15 +198,8 @@ public class WorkingNote {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
// 加载富文本格式信息
mRichTextFormatInfo = cursor.getString(DATA_RICH_TEXT_FORMAT_COLUMN);
}
//如果是图片类型
else if (DataConstants.IMAGE.equals(type)) {
// 图片数据目前我们只是加载数据实际图片显示在UI层处理
String imagePath = cursor.getString(DATA_IMAGE_PATH_COLUMN);
Log.d(TAG, "Loaded image data: " + imagePath);
}
//如果是通话记录类型
else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
@ -355,6 +340,61 @@ 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)) {
@ -363,18 +403,6 @@ public class WorkingNote {
}
}
//设置富文本格式
public void setRichTextFormat(String formatInfo) {
if (!TextUtils.equals(mRichTextFormatInfo, formatInfo)) {
mRichTextFormatInfo = formatInfo;
mNote.setRichTextFormat(formatInfo);
}
}
public String getRichTextFormat() {
return mRichTextFormatInfo;
}
// 将笔记转换为通话笔记(设置通话相关数据)
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
@ -385,12 +413,11 @@ 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)) {
@ -398,31 +425,31 @@ public class WorkingNote {
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;
}
@ -471,10 +498,14 @@ public class WorkingNote {
return mWidgetType;
}
public Note getNote() {
return mNote;
public String getTag() {
return mTag;
}
public void setTag(String tag) {
mTag = tag;
mNote.setNoteValue(NoteColumns.TAG, tag);
}
// 笔记设置变更监听器接口(定义属性变化时的回调方法)
public interface NoteSettingChangedListener {
@ -487,13 +518,12 @@ 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
*/

@ -0,0 +1,624 @@
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) + "...";
}
}

@ -17,9 +17,7 @@
package net.micode.notes.tool;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
@ -28,9 +26,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* SpanSpannable
*/
@ -55,11 +50,9 @@ public class ImageSpanUtils {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
// 获取图片URI(如果有的话)
// 获取图片URI
Drawable drawable = span.getDrawable();
if (drawable != null) {
// 这里我们假设图片信息已经在其他地方存储,只需记录位置和路径
// 在实际实现中,你可能需要将图片保存到特定位置并记录路径
spanObj.put("type", "image");
spanObj.put("start", start);
spanObj.put("end", end);

@ -9,7 +9,6 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

@ -0,0 +1,503 @@
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();
}
}

@ -11,33 +11,17 @@ 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 final String KEY_LOGIN_TIMESTAMP = "login_timestamp";
private static volatile UserManager instance; // 使用volatile关键字确保多线程环境下的可见性
private static UserManager instance;
private SharedPreferences sharedPreferences;
private Context applicationContext;
private UserManager(Context context) {
if (context != null) {
this.applicationContext = context.getApplicationContext();
if (this.applicationContext != null) {
this.sharedPreferences = this.applicationContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
}
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public static UserManager getInstance(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
// 双重检查锁定模式,确保线程安全
public static synchronized UserManager getInstance(Context context) {
if (instance == null) {
synchronized (UserManager.class) {
if (instance == null) {
instance = new UserManager(context.getApplicationContext());
}
}
instance = new UserManager(context.getApplicationContext());
}
return instance;
}
@ -49,7 +33,6 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, true)
.putString(KEY_CURRENT_USER, username)
.putLong(KEY_LOGIN_TIMESTAMP, System.currentTimeMillis()) // 保存当前登录时间
.apply();
}
@ -67,22 +50,6 @@ public class UserManager {
return sharedPreferences.getString(KEY_CURRENT_USER, "");
}
/**
* 3
*/
public boolean isLoginValid() {
if (!isLoggedIn()) {
return false;
}
long loginTime = sharedPreferences.getLong(KEY_LOGIN_TIMESTAMP, 0);
long currentTime = System.currentTimeMillis();
// 3天 = 3 * 24 * 60 * 60 * 1000 毫秒
long threeDaysInMillis = 3L * 24 * 60 * 60 * 1000;
return (currentTime - loginTime) < threeDaysInMillis;
}
/**
* 退
*/
@ -90,7 +57,6 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, false)
.remove(KEY_CURRENT_USER)
.remove(KEY_LOGIN_TIMESTAMP)
.apply();
}
}

@ -73,7 +73,12 @@ public class AlarmInitReceiver extends BroadcastReceiver {
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId));
// 创建待定意图,用于闹钟管理器
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 在 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);
// 获取闹钟管理器服务
AlarmManager alarmManager = (AlarmManager) context

@ -3,13 +3,17 @@ 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;
@ -22,18 +26,16 @@ import net.micode.notes.tool.UserManager;
public class LoginRegisterActivity extends AppCompatActivity {
// UI组件
private ImageView ivLogo;
private TextView tvLoginTab;
private TextView tvRegisterTab;
private View tabIndicator;
private EditText etUsername;
private EditText etPassword;
private EditText etConfirmPassword;
private ImageView ivPasswordVisibility;
private CheckBox cbRememberPassword;
private TextView tvErrorMessage;
private Button btnAction;
private TextView tvForgotPassword;
private LinearLayout confirmPasswordLayout;
// 数据库帮助类
private UserDatabaseHelper dbHelper;
@ -46,25 +48,16 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 密码可见性状态
private boolean isPasswordVisible = false;
private static final int ROOT_AUTH_REQUEST_CODE = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_register);
//通过getInstance获取已初始化的UserManager实例
UserManager userManager = UserManager.getInstance(this);
//安全调用先判空再调用方法避免null
// 检查是否已登录且在有效期内,如果已登录且在有效期内则直接跳转到主页面
if (userManager != null && userManager.isLoginValid()) {
startActivity(new Intent(this, NotesListActivity.class));
finish();
return;
}
// 初始化用户管理器
userManager = UserManager.getInstance(this);
// 每次都显示登录/注册界面,不自动登录
initViews();
initListeners();
updateUIForLoginMode();
@ -74,24 +67,22 @@ public class LoginRegisterActivity extends AppCompatActivity {
*
*/
private void initViews() {
ivLogo = findViewById(R.id.iv_logo);
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);
etConfirmPassword = findViewById(R.id.et_confirm_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);
confirmPasswordLayout = findViewById(R.id.confirm_password_layout);
// 初始化数据库帮助类
dbHelper = UserDatabaseHelper.getInstance(this);
// 初始化用户管理器
userManager = UserManager.getInstance(this);
// 加载记住的密码
loadRememberedPassword();
}
/**
@ -131,8 +122,7 @@ public class LoginRegisterActivity extends AppCompatActivity {
if (isLoginMode) {
performLogin();
} else {
// 注册前需要验证根用户权限
showRootAuthDialog();
performRegister();
}
}
});
@ -146,15 +136,6 @@ public class LoginRegisterActivity extends AppCompatActivity {
});
}
/**
*
*/
private void showRootAuthDialog() {
Intent intent = new Intent(this, SplashActivity.class);
intent.putExtra("SHOW_ROOT_AUTH_DIALOG", true);
startActivityForResult(intent, ROOT_AUTH_REQUEST_CODE);
}
/**
* UI
*/
@ -165,26 +146,24 @@ public class LoginRegisterActivity extends AppCompatActivity {
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.darker_gray));
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 40; // 左侧位置 (登录tab的位置)
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 40; // 左侧位置
tabIndicator.setLayoutParams(params);
btnAction.setText("登录");
tvForgotPassword.setVisibility(View.VISIBLE);
confirmPasswordLayout.setVisibility(View.GONE); // 隐藏确认密码输入框
} else {
// 注册模式
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
tvLoginTab.setTextColor(getResources().getColor(android.R.color.darker_gray));
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 120; // 右侧位置 (注册tab的位置)
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tabIndicator.getLayoutParams();
params.leftMargin = 120; // 右侧位置
tabIndicator.setLayoutParams(params);
btnAction.setText("注册");
tvForgotPassword.setVisibility(View.GONE);
confirmPasswordLayout.setVisibility(View.VISIBLE); // 显示确认密码输入框
}
// 清除错误消息
@ -198,10 +177,10 @@ public class LoginRegisterActivity extends AppCompatActivity {
isPasswordVisible = !isPasswordVisible;
if (isPasswordVisible) {
etPassword.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
ivPasswordVisibility.setImageResource(R.drawable.ic_eye_open);
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(R.drawable.ic_eye_closed);
ivPasswordVisibility.setImageResource(android.R.drawable.ic_lock_lock);
}
// 将光标移到末尾
@ -231,6 +210,13 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 登录成功,保存登录状态
userManager.saveLoginStatus(username);
// 保存记住的密码
if (cbRememberPassword.isChecked()) {
saveRememberedPassword(username, password);
} else {
clearRememberedPassword();
}
// 跳转到主页面
Intent intent = new Intent(this, NotesListActivity.class);
startActivity(intent);
@ -241,6 +227,45 @@ public class LoginRegisterActivity extends AppCompatActivity {
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();
}
/**
*
@ -248,7 +273,6 @@ public class LoginRegisterActivity extends AppCompatActivity {
private void performRegister() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
@ -270,16 +294,6 @@ public class LoginRegisterActivity extends AppCompatActivity {
showError("密码长度不能少于6位");
return;
}
if (TextUtils.isEmpty(confirmPassword)) {
showError("请确认密码");
return;
}
if (!password.equals(confirmPassword)) {
showError("两次输入的密码不一致");
return;
}
// 检查用户名是否已存在
if (dbHelper.isUsernameExists(username)) {
@ -292,10 +306,9 @@ public class LoginRegisterActivity extends AppCompatActivity {
if (success) {
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
// 自动切换到登录模式并清空输入框
// 自动切换到登录模式
isLoginMode = true;
updateUIForLoginMode();
clearInputFields(); // 清空输入框
} else {
showError("注册失败,请重试");
}
@ -308,16 +321,6 @@ public class LoginRegisterActivity extends AppCompatActivity {
tvErrorMessage.setText(message);
tvErrorMessage.setVisibility(View.VISIBLE);
}
/**
*
*/
private void clearInputFields() {
etUsername.setText("");
etPassword.setText("");
etConfirmPassword.setText("");
tvErrorMessage.setVisibility(View.GONE);
}
/**
*
@ -334,24 +337,4 @@ public class LoginRegisterActivity extends AppCompatActivity {
})
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ROOT_AUTH_REQUEST_CODE) {
if (resultCode == RESULT_OK && data != null) {
boolean authResult = data.getBooleanExtra("ROOT_AUTH_RESULT", false);
if (authResult) {
// 根用户验证成功,执行注册
performRegister();
} else {
Toast.makeText(this, "根用户验证失败,无法注册新用户", Toast.LENGTH_SHORT).show();
}
} else if (resultCode == RESULT_CANCELED) {
// 用户取消了验证
Toast.makeText(this, "已取消根用户验证", Toast.LENGTH_SHORT).show();
}
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -41,7 +41,7 @@ import java.util.Map;
*
*
*/
public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex; // 当前编辑框在清单列表中的索引位置
private int mSelectionStartBeforeDelete; // 记录删除操作前光标起始位置
@ -64,8 +64,6 @@ public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
* NoteEditActivity
*/
public interface OnTextViewChangeListener {
void onBackPressedDispatcher();
/**
*
* @param index

@ -48,7 +48,8 @@ public class NoteItemData {
NoteColumns.SNIPPET, // 内容摘要/片段
NoteColumns.TYPE, // 类型(笔记、文件夹、系统文件夹等)
NoteColumns.WIDGET_ID, // 关联的小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.WIDGET_TYPE, // 小部件的类型
NoteColumns.TAG, // 标签
};
/**
@ -67,6 +68,7 @@ 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
@ -81,6 +83,8 @@ public class NoteItemData {
private int mType; // 类型(笔记、文件夹、系统文件夹等)
private int mWidgetId; // 关联的桌面小部件ID
private int mWidgetType; // 桌面小部件的类型
private String mTag; // 标签
private boolean mIsCompleted; // 便签是否已完成
// 额外添加的业务逻辑相关字段(不直接来自数据库)
private String mName; // 联系人姓名(如果是通话记录笔记)
@ -110,6 +114,9 @@ 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, "");
@ -331,8 +338,40 @@ public class NoteItemData {
}
/**
* /
* @return
*
* @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
*/
public String getSnippet() {
return mSnippet;
@ -346,6 +385,14 @@ public class NoteItemData {
return (mAlertDate > 0);
}
/**
*
* @return
*/
public String getTag() {
return mTag;
}
/**
*
*
@ -354,6 +401,14 @@ public class NoteItemData {
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* 便
* @return 便true
*/
public boolean isCompleted() {
return mIsCompleted;
}
/**
*

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@
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;
@ -27,7 +28,6 @@ import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.PrivacyLockManager;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
@ -36,10 +36,10 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标(时钟或通话记录图标)
private ImageView mLock; // 锁图标
private TextView mTitle; // 标题/内容文本
private TextView mTime; // 修改时间文本
private TextView mCallName; // 通话记录联系人姓名
private TextView mTag; // 标签文本
private NoteItemData mItemData; // 绑定的数据对象
private CheckBox mCheckBox; // 多选模式下的复选框
@ -53,10 +53,10 @@ public class NotesListItem extends LinearLayout {
inflate(context, R.layout.note_item, this);
// 初始化各个子视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mLock = (ImageView) findViewById(R.id.iv_lock_icon);
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,12 +117,26 @@ public class NotesListItem extends LinearLayout {
data.getNotesCount()));
mAlert.setVisibility(View.GONE); // 文件夹不显示提醒图标
} else {
// 普通笔记显示:内容摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 普通笔记显示:标题
String title = data.getTitle();
String content = data.getContentSnippet();
if (TextUtils.isEmpty(title)) {
// 如果没有标题,显示内容摘要
mTitle.setText(DataUtils.getFormattedSnippet(content));
} else {
// 如果有标题,显示标题
mTitle.setText(title);
}
// 根据是否有提醒设置提醒图标
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);
}
@ -132,15 +146,32 @@ public class NotesListItem extends LinearLayout {
// 设置相对时间显示(如"2分钟前"、"昨天"等)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 显示锁图标(如果便签被锁定)
showLockIcon(context, data);
// 设置标签显示
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);
}
/**
*
*
*
* @param data
*/
@ -167,20 +198,6 @@ public class NotesListItem extends LinearLayout {
}
}
/**
* 便
* @param context
* @param data
*/
private void showLockIcon(Context context, NoteItemData data) {
PrivacyLockManager privacyLockManager = new PrivacyLockManager(context);
if (privacyLockManager.isNoteLocked(data.getId())) {
mLock.setVisibility(View.VISIBLE);
} else {
mLock.setVisibility(View.GONE);
}
}
/**
*
* @return NoteItemData

@ -27,7 +27,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
@ -43,8 +42,6 @@ import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
@ -77,7 +74,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
/**
* Activity
*/
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@ -93,7 +89,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
registerReceiver(mReceiver, filter);
mOriAccounts = null;
// 加载设置页面的自定义头部布局

@ -0,0 +1,277 @@
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);
}
}
}

@ -3,47 +3,58 @@ 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;
import net.micode.notes.tool.UserManager;
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);
// 开始淡入动画
WaveAnimation.applyFadeInAnimation(text);
// 3秒后根据登录状态跳转
new Handler().postDelayed(() -> {
UserManager userManager = UserManager.getInstance(this);
boolean isLoggedIn = userManager.isLoggedIn();
if (isLoggedIn) {
// 已登录,直接进入主界面
Intent intent = new Intent(SplashActivity.this, NotesListActivity.class);
startActivity(intent);
} else {
// 未登录,跳转到注册登录界面
Intent intent = new Intent(SplashActivity.this, LoginRegisterActivity.class);
startActivity(intent);
// 加载文字滑动动画
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();
}
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}, 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) {

Loading…
Cancel
Save