diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index bffc164..10c78a7 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -30,6 +30,12 @@ android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="adjustPan" android:exported="true"> + + + @@ -181,6 +187,14 @@ android:exported="true"> + + + diff --git a/src/main/java/net/micode/notes/data/Notes.java b/src/main/java/net/micode/notes/data/Notes.java index a204483..ca9a1ed 100644 --- a/src/main/java/net/micode/notes/data/Notes.java +++ b/src/main/java/net/micode/notes/data/Notes.java @@ -83,6 +83,14 @@ public class Notes { public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件类型 public static final int TYPE_WIDGET_2X = 0; // 2x2小部件类型 public static final int TYPE_WIDGET_4X = 1; // 4x4小部件类型 + + /** + * 隐私空间相关常量 + * - TYPE_PRIVACY_SPACE:隐私空间类型 + * - ID_PRIVACY_SPACE_FOLDER:隐私空间根文件夹ID + */ + public static final int TYPE_PRIVACY_SPACE = 4; // 隐私空间类型 + public static final int ID_PRIVACY_SPACE_FOLDER = -4; // 隐私空间根文件夹ID /** * 数据类型常量类,定义了不同类型的便签数据 @@ -273,6 +281,12 @@ public class Notes { *

类型: INTEGER

*/ public static final String DATA1 = "data1"; + + /** + * 隐私空间ID,用于标识便签所属的隐私空间 + *

类型: TEXT

+ */ + public static final String PRIVACY_SPACE_ID = "privacy_space_id"; } /** diff --git a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index 39bf475..cb6e34e 100644 --- a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -36,7 +36,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; // 数据库版本号 - private static final int DB_VERSION = 10; + private static final int DB_VERSION = 11; // 数据库表名定义 public interface TABLE { @@ -96,7 +96,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.IS_LOCKED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''," + NoteColumns.TAGS + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.DATA1 + " INTEGER NOT NULL DEFAULT 0" + + NoteColumns.DATA1 + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.PRIVACY_SPACE_ID + " TEXT NOT NULL DEFAULT ''" + ")"; // 创建数据表的SQL语句 @@ -328,6 +329,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); + + // 创建隐私空间根文件夹:用于存储隐私空间 + values.clear(); + values.put(NoteColumns.ID, Notes.ID_PRIVACY_SPACE_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); } /** @@ -448,6 +455,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { upgradeToV10(db); oldVersion++; } + + if (oldVersion == 10) { + upgradeToV11(db); + oldVersion++; + } if (reCreateTriggers) { reCreateNoteTableTriggers(db); @@ -580,4 +592,20 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.DATA1 + " INTEGER NOT NULL DEFAULT 0"); } + + /** + * 将数据库从v10升级到v11 + * 此版本升级添加了PRIVACY_SPACE_ID字段,用于存储便签所属的隐私空间ID + * @param db SQLite数据库实例 + */ + private void upgradeToV11(SQLiteDatabase db) { + // 为笔记表添加PRIVACY_SPACE_ID字段 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.PRIVACY_SPACE_ID + + " TEXT NOT NULL DEFAULT ''"); + // 添加隐私空间根文件夹 + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, Notes.ID_PRIVACY_SPACE_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } } diff --git a/src/main/java/net/micode/notes/model/WorkingNote.java b/src/main/java/net/micode/notes/model/WorkingNote.java index 40bab6a..9eefdaa 100644 --- a/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/main/java/net/micode/notes/model/WorkingNote.java @@ -132,6 +132,11 @@ public class WorkingNote { * 标题,便签的标题 */ private String mTitle; + + /** + * 隐私空间ID,用于标识便签所属的隐私空间 + */ + private String mPrivacySpaceId; /** * 数据投影数组,用于从数据库中查询便签数据 @@ -160,7 +165,8 @@ public class WorkingNote { NoteColumns.LOCK_PASSWORD, // 锁定密码 NoteColumns.PASSWORD_TYPE, // 密码类型 NoteColumns.NUMERIC_PASSWORD, // 数字密码 - NoteColumns.TITLE // 标题 + NoteColumns.TITLE, // 标题 + NoteColumns.PRIVACY_SPACE_ID // 隐私空间ID }; /** @@ -184,6 +190,7 @@ public class WorkingNote { private static final int NOTE_PASSWORD_TYPE_COLUMN = 8; // 密码类型列索引 private static final int NOTE_NUMERIC_PASSWORD_COLUMN = 9; // 数字密码列索引 private static final int NOTE_TITLE_COLUMN = 10; // 标题列索引 + private static final int NOTE_PRIVACY_SPACE_ID_COLUMN = 11; // 隐私空间ID列索引 /** * 构造方法,创建一个新的便签 @@ -198,6 +205,7 @@ public class WorkingNote { mNote = new Note(); // 创建便签对象 mNoteId = 0; // 新便签ID为0 mIsDeleted = false; // 初始化为未删除 + mPrivacySpaceId = ""; // 初始化为空 mMode = 0; // 初始化为普通模式 mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 初始化为无效小部件类型 } @@ -241,6 +249,10 @@ public class WorkingNote { mPasswordType = cursor.getString(NOTE_PASSWORD_TYPE_COLUMN); mNumericPassword = cursor.getString(NOTE_NUMERIC_PASSWORD_COLUMN); mTitle = cursor.getString(NOTE_TITLE_COLUMN); + mPrivacySpaceId = cursor.getString(NOTE_PRIVACY_SPACE_ID_COLUMN); + if (mPrivacySpaceId == null) { + mPrivacySpaceId = ""; + } } cursor.close(); } else { @@ -576,6 +588,25 @@ public class WorkingNote { return mWidgetType; } + /** + * 设置隐私空间ID + * @param privacySpaceId 隐私空间ID + */ + public void setPrivacySpaceId(String privacySpaceId) { + if (!TextUtils.equals(mPrivacySpaceId, privacySpaceId)) { + mPrivacySpaceId = privacySpaceId; + mNote.setNoteValue(NoteColumns.PRIVACY_SPACE_ID, privacySpaceId); + } + } + + /** + * 获取隐私空间ID + * @return 隐私空间ID + */ + public String getPrivacySpaceId() { + return mPrivacySpaceId; + } + /** * 设置便签锁定状态 * @param locked 是否锁定 diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 97fa85e..013726d 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -300,15 +300,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } // 处理创建或编辑便签操作 - else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // 获取参数 - long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); - int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, - Notes.TYPE_WIDGET_INVALIDE); - int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, - ResourceParser.getDefaultBgId(this)); + else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 获取参数 + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE); + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + String privacySpaceId = intent.getStringExtra("privacy_space_id"); // 处理通话记录便签 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); @@ -332,11 +333,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); mWorkingNote.convertToCallNote(phoneNumber, callDate); + // 设置隐私空间ID + if (!TextUtils.isEmpty(privacySpaceId)) { + mWorkingNote.setPrivacySpaceId(privacySpaceId); + } } } else { // 创建普通新便签 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); + // 设置隐私空间ID + if (!TextUtils.isEmpty(privacySpaceId)) { + mWorkingNote.setPrivacySpaceId(privacySpaceId); + } } // 设置软键盘模式为可见状态 diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java index 35f8bfb..64dfc2f 100644 --- a/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -50,6 +50,7 @@ import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; import android.view.View.OnTouchListener; import android.view.KeyEvent; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; @@ -62,8 +63,12 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupMenu; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; +import android.widget.GridView; +import android.widget.BaseAdapter; +import android.view.ViewGroup; import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -96,11 +101,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private static final int MENU_FOLDER_CHANGE_NAME = 2; private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + private static final String PREFERENCE_BACKGROUND = "net.micode.notes.background"; + private static final String BACKGROUND_TYPE_DEFAULT = "default"; + private static final String BACKGROUND_TYPE_ALBUM = "album"; + private static final int REQUEST_CODE_PICK_IMAGE = 105; private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER }; + // 背景选项 + private static final String[] BACKGROUND_OPTIONS = { + "高山流水", "风中树叶", "长河落日" + }; + private static final int[] BACKGROUND_RESOURCES = { + R.drawable.background_mountain, R.drawable.background_leaves, R.drawable.background_sunset + }; + private ListEditState mState; private BackgroundQueryHandler mBackgroundQueryHandler; @@ -110,6 +127,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private ListView mNotesListView; private Button mAddNewNote; + private Button mPrivacyBackButton; private boolean mDispatch; @@ -135,6 +153,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.TYPE + "<>" + Notes.TYPE_TODO + " AND " + NoteColumns.PARENT_ID + "=?) OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; + // 隐私空间相关常量 + private static final int REQUEST_CODE_PRIVACY_SPACE = 104; + private String mCurrentPrivacySpaceId = ""; + private final static int REQUEST_CODE_OPEN_NODE = 102; private final static int REQUEST_CODE_NEW_NODE = 103; @@ -146,11 +168,30 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private String mSearchQuery; private boolean mIsSearching; + // 背景相关变量 + private View mRootView; + private RelativeLayout mBackgroundContainer; + private LinearLayout mMainContent; + private String mCurrentBackgroundType = BACKGROUND_TYPE_DEFAULT; + private int mCurrentBackgroundResource = R.drawable.list_background; + private String mCurrentBackgroundPath = ""; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_list); + + // 获取隐私空间ID + Intent intent = getIntent(); + if (intent != null) { + mCurrentPrivacySpaceId = intent.getStringExtra("privacy_space_id"); + if (mCurrentPrivacySpaceId == null) { + mCurrentPrivacySpaceId = ""; + } + } + initResources(); + initBackground(); /** * Insert an introduction when user firstly use this application @@ -160,9 +201,36 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK - && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { - mNotesListAdapter.changeCursor(null); + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE) { + mNotesListAdapter.changeCursor(null); + } else if (requestCode == REQUEST_CODE_PRIVACY_SPACE) { + // 处理从隐私空间返回的结果 + if (data != null) { + String privacySpaceId = data.getStringExtra("privacy_space_id"); + if (!TextUtils.isEmpty(privacySpaceId)) { + mCurrentPrivacySpaceId = privacySpaceId; + updatePrivacySpaceUI(); + startAsyncNotesListQuery(); + } + } + } else if (requestCode == REQUEST_CODE_PICK_IMAGE) { + // 处理从相册选择图片的结果 + if (data != null && data.getData() != null) { + try { + // 保存图片路径 + mCurrentBackgroundType = BACKGROUND_TYPE_ALBUM; + mCurrentBackgroundPath = data.getData().toString(); + // 更新背景 + updateBackground(); + // 保存设置 + saveBackgroundSetting(); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, "图片加载失败", Toast.LENGTH_SHORT).show(); + } + } + } } else { super.onActivityResult(requestCode, resultCode, data); } @@ -290,13 +358,46 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mAddNewNote = (Button) findViewById(R.id.btn_add_note); mAddNewNote.setOnClickListener(this); mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mPrivacyBackButton = (Button) findViewById(R.id.btn_privacy_back); + mPrivacyBackButton.setOnClickListener(this); mDispatch = false; mDispatchY = 0; mOriginY = 0; mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + mMainContent = (LinearLayout) findViewById(R.id.main_content); + // 获取根视图作为背景容器 + mBackgroundContainer = (RelativeLayout) findViewById(R.id.note_list_root); + if (mBackgroundContainer == null) { + // 如果找不到,使用content view + View contentView = getWindow().getDecorView().findViewById(android.R.id.content); + if (contentView instanceof ViewGroup) { + ViewGroup contentViewGroup = (ViewGroup) contentView; + if (contentViewGroup.getChildCount() > 0) { + View childView = contentViewGroup.getChildAt(0); + if (childView instanceof RelativeLayout) { + mBackgroundContainer = (RelativeLayout) childView; + } + } + } + } + // 最后的备用方案 + if (mBackgroundContainer == null) { + // 如果还是找不到,使用整个窗口的装饰视图作为背景容器 + mBackgroundContainer = new RelativeLayout(this); + ViewGroup decorView = (ViewGroup) getWindow().getDecorView(); + View contentView = decorView.findViewById(android.R.id.content); + if (contentView != null) { + decorView.removeView(contentView); + mBackgroundContainer.addView(contentView); + decorView.addView(mBackgroundContainer); + } + } mState = ListEditState.NOTE_LIST; mModeCallBack = new ModeCallback(); + // 检查是否处于隐私空间中 + updatePrivacySpaceUI(); + // 初始化界面切换栏 mNotesTab = (TextView) findViewById(R.id.notes_tab); mTodoTab = (TextView) findViewById(R.id.todo_tab); @@ -711,6 +812,57 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + // 添加隐私空间筛选条件 + if (!TextUtils.isEmpty(mCurrentPrivacySpaceId)) { + // 在隐私空间中,只显示该隐私空间的便签 + String privacyCondition = NoteColumns.PRIVACY_SPACE_ID + "=?"; + if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) { + // 根文件夹需要特殊处理 + if (!TextUtils.isEmpty(mSearchQuery) || !TextUtils.isEmpty(mSelectedTag)) { + // 有搜索关键词或标签的情况 + baseSelection = "(" + baseSelection + ") AND " + privacyCondition; + } else { + // 没有搜索关键词和标签的情况 + baseSelection = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=? AND " + privacyCondition + ")"; + // 重新构建参数列表 + argsList.clear(); + argsList.add(String.valueOf(mCurrentFolderId)); + argsList.add(mCurrentPrivacySpaceId); + } + } else { + // 普通文件夹 + if (!TextUtils.isEmpty(mSearchQuery) || !TextUtils.isEmpty(mSelectedTag)) { + // 有搜索关键词或标签的情况 + baseSelection = "(" + baseSelection + ") AND " + privacyCondition; + } else { + // 没有搜索关键词和标签的情况 + baseSelection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "<>" + Notes.TYPE_TODO + " AND " + privacyCondition; + // 重新构建参数列表 + argsList.clear(); + argsList.add(String.valueOf(mCurrentFolderId)); + argsList.add(mCurrentPrivacySpaceId); + } + } + if (!TextUtils.isEmpty(mSearchQuery) || !TextUtils.isEmpty(mSelectedTag)) { + // 有搜索关键词或标签的情况,需要添加隐私空间ID参数 + argsList.add(mCurrentPrivacySpaceId); + } + } else { + // 不在隐私空间中,只显示非隐私空间的便签 + String privacyCondition = "(" + NoteColumns.PRIVACY_SPACE_ID + "='' OR " + NoteColumns.PRIVACY_SPACE_ID + " IS NULL)"; + if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) { + // 根文件夹需要特殊处理 + baseSelection = "(" + baseSelection + ") AND " + privacyCondition; + } else { + // 普通文件夹 + if (baseSelection.contains("AND")) { + baseSelection += " AND " + privacyCondition; + } else { + baseSelection += " WHERE " + privacyCondition; + } + } + } + selection = baseSelection; selectionArgs = argsList.toArray(new String[argsList.size()]); @@ -769,6 +921,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + intent.putExtra("privacy_space_id", mCurrentPrivacySpaceId); this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } @@ -856,6 +1009,29 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt public void onClick(View v) { if (v.getId() == R.id.btn_add_note) { createNewNote(); + } else if (v.getId() == R.id.btn_privacy_back) { + // 从隐私空间返回普通空间 + mCurrentPrivacySpaceId = ""; + updatePrivacySpaceUI(); + startAsyncNotesListQuery(); + } + } + + /** + * 更新隐私空间相关的UI元素 + */ + private void updatePrivacySpaceUI() { + if (!TextUtils.isEmpty(mCurrentPrivacySpaceId)) { + // 在隐私空间中,显示隐私空间名称 + mTitleBar.setText("隐私空间"); + mTitleBar.setVisibility(View.VISIBLE); + // 显示隐私空间返回按钮 + mPrivacyBackButton.setVisibility(View.VISIBLE); + } else { + // 不在隐私空间中,隐藏标题栏或显示默认标题 + mTitleBar.setVisibility(View.GONE); + // 隐藏隐私空间返回按钮 + mPrivacyBackButton.setVisibility(View.GONE); } } @@ -1060,6 +1236,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // set sync or sync_cancel menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); + // 添加背景设置选项 + addBackgroundMenuItems(menu); } else if (mState == ListEditState.SUB_FOLDER) { getMenuInflater().inflate(R.menu.sub_folder, menu); } else if (mState == ListEditState.CALL_RECORD_FOLDER) { @@ -1094,6 +1272,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt onSearchRequested(); } else if (item.getItemId() == R.id.menu_trash) { startTrashActivity(); + } else if (item.getItemId() == R.id.menu_privacy_space) { + if (!TextUtils.isEmpty(mCurrentPrivacySpaceId)) { + // 从隐私空间返回普通空间 + Intent intent = new Intent(this, NotesListActivity.class); + intent.putExtra("privacy_space_id", ""); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } else { + // 打开隐私空间 + Intent intent = new Intent(this, PrivacySpaceActivity.class); + startActivityForResult(intent, REQUEST_CODE_PRIVACY_SPACE); + } } return true; } @@ -1413,4 +1604,150 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } + + /** + * 初始化背景 + */ + private void initBackground() { + // 加载保存的背景设置 + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + mCurrentBackgroundType = sp.getString(PREFERENCE_BACKGROUND + "_type", BACKGROUND_TYPE_DEFAULT); + mCurrentBackgroundPath = sp.getString(PREFERENCE_BACKGROUND + "_path", ""); + + if (BACKGROUND_TYPE_DEFAULT.equals(mCurrentBackgroundType)) { + int backgroundIndex = sp.getInt(PREFERENCE_BACKGROUND + "_index", 0); + if (backgroundIndex >= 0 && backgroundIndex < BACKGROUND_RESOURCES.length) { + mCurrentBackgroundResource = BACKGROUND_RESOURCES[backgroundIndex]; + } + } + + // 更新背景 + updateBackground(); + } + + /** + * 更新背景 + */ + private void updateBackground() { + if (mBackgroundContainer == null) { + return; + } + + try { + if (BACKGROUND_TYPE_DEFAULT.equals(mCurrentBackgroundType)) { + // 使用默认背景 + mBackgroundContainer.setBackgroundResource(mCurrentBackgroundResource); + } else if (BACKGROUND_TYPE_ALBUM.equals(mCurrentBackgroundType) && !TextUtils.isEmpty(mCurrentBackgroundPath)) { + // 使用相册图片作为背景 + try { + android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream( + getContentResolver().openInputStream(android.net.Uri.parse(mCurrentBackgroundPath))); + if (bitmap != null) { + // 计算屏幕尺寸 + android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int screenWidth = displayMetrics.widthPixels; + int screenHeight = displayMetrics.heightPixels; + + // 缩放图片以适配屏幕 + android.graphics.Bitmap scaledBitmap = android.graphics.Bitmap.createScaledBitmap( + bitmap, screenWidth, screenHeight, true); + + android.graphics.drawable.BitmapDrawable drawable = new android.graphics.drawable.BitmapDrawable(getResources(), scaledBitmap); + // 设置背景图片的缩放方式 + drawable.setTileModeXY(android.graphics.Shader.TileMode.CLAMP, android.graphics.Shader.TileMode.CLAMP); + mBackgroundContainer.setBackground(drawable); + + // 释放原始 bitmap + if (bitmap != scaledBitmap) { + bitmap.recycle(); + } + } + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, "图片加载失败", Toast.LENGTH_SHORT).show(); + // 加载失败时使用默认背景 + mBackgroundContainer.setBackgroundResource(R.drawable.list_background); + } + } + } catch (Exception e) { + e.printStackTrace(); + // 加载失败时使用默认背景 + mBackgroundContainer.setBackgroundResource(R.drawable.list_background); + } + } + + /** + * 保存背景设置 + */ + private void saveBackgroundSetting() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(PREFERENCE_BACKGROUND + "_type", mCurrentBackgroundType); + editor.putString(PREFERENCE_BACKGROUND + "_path", mCurrentBackgroundPath); + + if (BACKGROUND_TYPE_DEFAULT.equals(mCurrentBackgroundType)) { + // 找到当前背景资源的索引 + int index = 0; + for (int i = 0; i < BACKGROUND_RESOURCES.length; i++) { + if (BACKGROUND_RESOURCES[i] == mCurrentBackgroundResource) { + index = i; + break; + } + } + editor.putInt(PREFERENCE_BACKGROUND + "_index", index); + } + + editor.apply(); + } + + /** + * 显示背景选择对话框 + */ + private void showBackgroundSelectorDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("选择背景"); + + // 创建背景选项数组,添加从相册选择的选项 + String[] options = new String[BACKGROUND_OPTIONS.length + 1]; + System.arraycopy(BACKGROUND_OPTIONS, 0, options, 0, BACKGROUND_OPTIONS.length); + options[BACKGROUND_OPTIONS.length] = "从相册选择"; + + builder.setItems(options, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which < BACKGROUND_OPTIONS.length) { + // 选择预设背景 + mCurrentBackgroundType = BACKGROUND_TYPE_DEFAULT; + mCurrentBackgroundResource = BACKGROUND_RESOURCES[which]; + mCurrentBackgroundPath = ""; + updateBackground(); + saveBackgroundSetting(); + Toast.makeText(NotesListActivity.this, "背景已更新", Toast.LENGTH_SHORT).show(); + } else { + // 从相册选择 + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE); + } + } + }); + + builder.show(); + } + + /** + * 在菜单中添加背景设置选项 + */ + private void addBackgroundMenuItems(Menu menu) { + MenuItem backgroundMenu = menu.add(0, 100, 0, "背景设置"); + backgroundMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + showBackgroundSelectorDialog(); + return true; + } + }); + } } diff --git a/src/main/java/net/micode/notes/ui/PrivacySpaceActivity.java b/src/main/java/net/micode/notes/ui/PrivacySpaceActivity.java new file mode 100644 index 0000000..f1829c8 --- /dev/null +++ b/src/main/java/net/micode/notes/ui/PrivacySpaceActivity.java @@ -0,0 +1,339 @@ +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.tool.LockPasswordUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class PrivacySpaceActivity extends Activity implements OnClickListener { + private static final String TAG = "PrivacySpaceActivity"; + + private static final String PREF_PRIVACY_SPACES = "privacy_spaces"; + private static final String PREF_CURRENT_SPACE = "current_privacy_space"; + + private LinearLayout mSpacesContainer; + private Button mAddSpaceButton; + private Button mBackButton; + + private ArrayList mPrivacySpaces; + private SharedPreferences mSharedPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.privacy_space_activity); + + initResources(); + loadPrivacySpaces(); + displayPrivacySpaces(); + } + + private void initResources() { + mSpacesContainer = (LinearLayout) findViewById(R.id.spaces_container); + mAddSpaceButton = (Button) findViewById(R.id.btn_add_space); + mBackButton = (Button) findViewById(R.id.btn_back); + + mAddSpaceButton.setOnClickListener(this); + mBackButton.setOnClickListener(this); + + mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + mPrivacySpaces = new ArrayList<>(); + } + + private void loadPrivacySpaces() { + // 从SharedPreferences加载隐私空间数据 + String spacesJson = mSharedPreferences.getString(PREF_PRIVACY_SPACES, ""); + if (!TextUtils.isEmpty(spacesJson)) { + // 解析JSON数据 + try { + // 简单的JSON解析逻辑 + if (spacesJson.startsWith("[")) { + spacesJson = spacesJson.substring(1, spacesJson.length() - 1); + String[] spaceArray = spacesJson.split("\\},\\{"); + for (String spaceStr : spaceArray) { + if (!spaceStr.startsWith("{")) { + spaceStr = "{" + spaceStr; + } + if (!spaceStr.endsWith("}")) { + spaceStr = spaceStr + "}"; + } + // 提取id、name和password + String id = extractValue(spaceStr, "id"); + String name = extractValue(spaceStr, "name"); + String password = extractValue(spaceStr, "password"); + if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(name)) { + mPrivacySpaces.add(new PrivacySpace(id, name, password)); + } + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to parse privacy spaces JSON", e); + // 解析失败,创建默认隐私空间 + createDefaultPrivacySpace(); + } + } else { + // 如果没有隐私空间,创建一个默认隐私空间 + createDefaultPrivacySpace(); + } + } + + private void createDefaultPrivacySpace() { + // 使用默认密码"123456",加密后存储 + String encryptedPassword = LockPasswordUtils.encryptPassword("123456"); + PrivacySpace defaultSpace = new PrivacySpace("1", "默认隐私空间", encryptedPassword); + mPrivacySpaces.add(defaultSpace); + savePrivacySpaces(); + } + + private String extractValue(String json, String key) { + String pattern = "\"" + key + "\":\"([^\"]*)\""; + java.util.regex.Pattern r = java.util.regex.Pattern.compile(pattern); + java.util.regex.Matcher m = r.matcher(json); + if (m.find()) { + return m.group(1); + } + return ""; + } + + private void savePrivacySpaces() { + // 将隐私空间数据保存到SharedPreferences + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("["); + for (int i = 0; i < mPrivacySpaces.size(); i++) { + PrivacySpace space = mPrivacySpaces.get(i); + jsonBuilder.append("{"); + jsonBuilder.append("\"id\":\"").append(space.getId()).append("\","); + jsonBuilder.append("\"name\":\"").append(space.getName()).append("\","); + jsonBuilder.append("\"password\":\"").append(space.getPassword()).append("\""); + jsonBuilder.append("}"); + if (i < mPrivacySpaces.size() - 1) { + jsonBuilder.append(","); + } + } + jsonBuilder.append("]"); + String spacesJson = jsonBuilder.toString(); + mSharedPreferences.edit().putString(PREF_PRIVACY_SPACES, spacesJson).apply(); + } + + private void displayPrivacySpaces() { + mSpacesContainer.removeAllViews(); + + for (final PrivacySpace space : mPrivacySpaces) { + View spaceItem = LayoutInflater.from(this).inflate(R.layout.privacy_space_item, null); + TextView spaceName = (TextView) spaceItem.findViewById(R.id.space_name); + Button enterButton = (Button) spaceItem.findViewById(R.id.btn_enter_space); + Button deleteButton = (Button) spaceItem.findViewById(R.id.btn_delete_space); + + spaceName.setText(space.getName()); + enterButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + enterPrivacySpace(space); + } + }); + + deleteButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + deletePrivacySpace(space); + } + }); + + mSpacesContainer.addView(spaceItem); + } + } + + private void enterPrivacySpace(final PrivacySpace space) { + // 显示密码输入对话框 + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null); + final EditText passwordEditText = (EditText) view.findViewById(R.id.et_password); + + builder.setTitle("请输入密码") + .setView(view) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel, null); + + final AlertDialog dialog = builder.create(); + dialog.show(); + + // 在show()之后获取确定按钮 + final Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + + // 设置密码输入监听 + passwordEditText.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 (okButton != null) { + okButton.setEnabled(s.length() > 0); + } + } + + @Override + public void afterTextChanged(Editable s) {} + }); + + // 自定义确定按钮点击事件 + if (okButton != null) { + okButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + String password = passwordEditText.getText().toString(); + if (verifyPassword(space, password)) { + dialog.dismiss(); + // 密码验证成功,进入隐私空间 + mSharedPreferences.edit().putString(PREF_CURRENT_SPACE, space.getId()).apply(); + + // 设置返回结果并结束活动 + Intent resultIntent = new Intent(); + resultIntent.putExtra("privacy_space_id", space.getId()); + setResult(RESULT_OK, resultIntent); + + // 结束活动,让 NotesListActivity 在 onActivityResult 方法中处理返回结果 + finish(); + } else { + Toast.makeText(PrivacySpaceActivity.this, "密码错误", Toast.LENGTH_SHORT).show(); + } + } + }); + } + } + + private boolean verifyPassword(PrivacySpace space, String password) { + // 使用LockPasswordUtils验证密码 + return LockPasswordUtils.verifyPassword(password, space.getPassword()); + } + + private void addPrivacySpace() { + // 显示创建隐私空间对话框 + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_create_space, null); + final EditText nameEditText = (EditText) view.findViewById(R.id.et_space_name); + final EditText passwordEditText = (EditText) view.findViewById(R.id.et_space_password); + + builder.setTitle("创建新隐私空间") + .setView(view) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel, null); + + final AlertDialog dialog = builder.create(); + dialog.show(); + + // 自定义确定按钮点击事件 + Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + okButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + String name = nameEditText.getText().toString(); + String password = passwordEditText.getText().toString(); + + if (TextUtils.isEmpty(name)) { + Toast.makeText(PrivacySpaceActivity.this, "请输入空间名称", Toast.LENGTH_SHORT).show(); + return; + } + + if (TextUtils.isEmpty(password) || password.length() < 6) { + Toast.makeText(PrivacySpaceActivity.this, "请输入至少6位密码", Toast.LENGTH_SHORT).show(); + return; + } + + // 创建新隐私空间,使用LockPasswordUtils加密密码 + String id = String.valueOf(System.currentTimeMillis()); + String encryptedPassword = LockPasswordUtils.encryptPassword(password); + PrivacySpace newSpace = new PrivacySpace(id, name, encryptedPassword); + mPrivacySpaces.add(newSpace); + + // 保存并显示 + savePrivacySpaces(); + displayPrivacySpaces(); + + dialog.dismiss(); + } + }); + } + + private void deletePrivacySpace(final PrivacySpace space) { + new AlertDialog.Builder(this) + .setTitle("删除隐私空间") + .setMessage("确定要删除\"" + space.getName() + "\"吗?删除后空间内的所有数据将被清除。") + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mPrivacySpaces.remove(space); + savePrivacySpaces(); + displayPrivacySpaces(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.btn_add_space) { + addPrivacySpace(); + } else if (v.getId() == R.id.btn_back) { + finish(); + } + } + + private class PrivacySpace { + private String mId; + private String mName; + private String mPassword; // 存储加密后的密码 + + public PrivacySpace(String id, String name) { + mId = id; + mName = name; + mPassword = ""; + } + + public PrivacySpace(String id, String name, String password) { + mId = id; + mName = name; + mPassword = password; + } + + public String getId() { + return mId; + } + + public String getName() { + return mName; + } + + public String getPassword() { + return mPassword; + } + + public void setPassword(String password) { + mPassword = password; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/micode/notes/ui/SplashActivity.java b/src/main/java/net/micode/notes/ui/SplashActivity.java new file mode 100644 index 0000000..f041775 --- /dev/null +++ b/src/main/java/net/micode/notes/ui/SplashActivity.java @@ -0,0 +1,74 @@ +package net.micode.notes.ui; + +import android.app.Activity; +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 net.micode.notes.R; + +public class SplashActivity extends Activity { + + private static final int SPLASH_DURATION = 2000; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.splash_activity); + + initAnimation(); + } + + private void initAnimation() { + ImageView logo = findViewById(R.id.splash_logo); + TextView appName = findViewById(R.id.splash_app_name); + + Animation splashAnimation = AnimationUtils.loadAnimation(this, R.anim.splash_animation); + + logo.startAnimation(splashAnimation); + appName.startAnimation(splashAnimation); + + splashAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + navigateToMainActivity(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + navigateToMainActivity(); + } + }, SPLASH_DURATION); + } + + private void navigateToMainActivity() { + Intent intent = new Intent(this, NotesListActivity.class); + startActivity(intent); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + finish(); + } + + @Override + protected void onPause() { + super.onPause(); + finish(); + } + + @Override + public void onBackPressed() { + } +} diff --git a/src/main/res/anim/splash_animation.xml b/src/main/res/anim/splash_animation.xml new file mode 100644 index 0000000..a96be7e --- /dev/null +++ b/src/main/res/anim/splash_animation.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/background_leaves.avif b/src/main/res/drawable/background_leaves.avif new file mode 100644 index 0000000..6b165db Binary files /dev/null and b/src/main/res/drawable/background_leaves.avif differ diff --git a/src/main/res/drawable/background_mountain.jpg b/src/main/res/drawable/background_mountain.jpg new file mode 100644 index 0000000..438357a Binary files /dev/null and b/src/main/res/drawable/background_mountain.jpg differ diff --git a/src/main/res/drawable/background_sunset.jpg b/src/main/res/drawable/background_sunset.jpg new file mode 100644 index 0000000..d585e1b Binary files /dev/null and b/src/main/res/drawable/background_sunset.jpg differ diff --git a/src/main/res/drawable/ic_arrow_back.xml b/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..072dcb3 --- /dev/null +++ b/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/ic_privacy_space.xml b/src/main/res/drawable/ic_privacy_space.xml new file mode 100644 index 0000000..c4ac138 --- /dev/null +++ b/src/main/res/drawable/ic_privacy_space.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/dialog_create_space.xml b/src/main/res/layout/dialog_create_space.xml new file mode 100644 index 0000000..61e3517 --- /dev/null +++ b/src/main/res/layout/dialog_create_space.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/dialog_password_input.xml b/src/main/res/layout/dialog_password_input.xml new file mode 100644 index 0000000..01d52de --- /dev/null +++ b/src/main/res/layout/dialog_password_input.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/note_list.xml b/src/main/res/layout/note_list.xml index c9afa71..1c850a5 100644 --- a/src/main/res/layout/note_list.xml +++ b/src/main/res/layout/note_list.xml @@ -17,6 +17,7 @@ + + +