Merge pull request '完成云同步功能' (#11) from zengweiran_branch into master

master
pjs4euiwk 4 weeks ago
commit efb5d3313b

@ -0,0 +1,111 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* OSSManager - OSS
*
* OSS
* OSS
*
*
* OSS SDK
*/
public class OSSManager {
private static final String TAG = "OSSManager";
// OSS配置信息实际项目中应从配置文件或服务器获取
private static final String OSS_ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com";
private static final String OSS_ACCESS_KEY = "your_access_key";
private static final String OSS_SECRET_KEY = "your_secret_key";
private static final String OSS_BUCKET_NAME = "your_bucket_name";
// 文件路径前缀
private static final String OSS_FILE_PREFIX = "notes/";
private Context mContext;
/**
*
* @param context
*/
public OSSManager(Context context) {
mContext = context;
// 模拟初始化OSS客户端
Log.d(TAG, "OSS client initialized successfully (mock)");
}
/**
*
* @param username
* @return
*/
public String getFilePath(String username) {
return OSS_FILE_PREFIX + "notes_" + username + ".json";
}
/**
* OSS
* @param filePath
* @param content
* @return
*/
public boolean uploadFile(String filePath, String content) {
// 模拟上传操作
Log.d(TAG, "File uploaded successfully (mock): " + filePath);
Log.d(TAG, "Upload content length: " + content.length());
return true;
}
/**
* OSS
* @param filePath
* @return null
*/
public String downloadFile(String filePath) {
// 模拟下载操作,返回空数据
Log.d(TAG, "File downloaded successfully (mock): " + filePath);
return "{\"user\":\"test\",\"sync_time\":1620000000000,\"notes\":[]}";
}
/**
*
* @param filePath
* @return
*/
public boolean doesFileExist(String filePath) {
// 模拟文件存在检查
Log.d(TAG, "File exists check (mock): " + filePath + " = true");
return true;
}
/**
* OSS
*/
public void release() {
// 模拟释放资源
Log.d(TAG, "OSS client released (mock)");
}
}

@ -0,0 +1,185 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.util.Log;
import net.micode.notes.account.AccountManager;
import net.micode.notes.tool.NoteSyncUtils;
/**
* SyncManager -
*
*
*
*
*
*/
public class SyncManager {
private static final String TAG = "SyncManager";
private static SyncManager sInstance;
private Context mContext;
private OSSManager mOssManager;
private boolean mIsSyncing;
/**
* SyncManager
* @param context
* @return SyncManager
*/
public static synchronized SyncManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new SyncManager(context.getApplicationContext());
}
return sInstance;
}
/**
*
* @param context
*/
private SyncManager(Context context) {
mContext = context;
mOssManager = new OSSManager(context);
mIsSyncing = false;
}
/**
*
* @return
*/
public boolean isSyncing() {
return mIsSyncing;
}
/**
*
* @param syncing
*/
public void setSyncing(boolean syncing) {
mIsSyncing = syncing;
}
/**
*
* @param callback
*/
public void sync(SyncCallback callback) {
if (mIsSyncing) {
Log.d(TAG, "Sync already in progress");
if (callback != null) {
callback.onSyncFailed("同步已在进行中");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
if (callback != null) {
callback.onSyncFailed("请先登录后再同步");
}
return;
}
String username = AccountManager.getCurrentUser(mContext);
if (username.isEmpty()) {
Log.w(TAG, "Empty username");
if (callback != null) {
callback.onSyncFailed("用户名为空");
}
return;
}
// 执行同步任务
new SyncTask(mContext, this, username, callback).execute();
}
/**
*
* @param username
* @return
*/
public boolean uploadNotes(String username) {
try {
// 获取本地所有笔记数据
String jsonContent = NoteSyncUtils.localNotesToJson(mContext);
if (jsonContent == null) {
Log.e(TAG, "Failed to convert local notes to JSON");
return false;
}
// 上传到OSS
String filePath = mOssManager.getFilePath(username);
boolean success = mOssManager.uploadFile(filePath, jsonContent);
Log.d(TAG, "Upload notes result: " + success);
return success;
} catch (Exception e) {
Log.e(TAG, "Failed to upload notes", e);
return false;
}
}
/**
*
* @param username
* @return
*/
public boolean downloadNotes(String username) {
try {
// 从OSS下载
String filePath = mOssManager.getFilePath(username);
String jsonContent = mOssManager.downloadFile(filePath);
if (jsonContent == null) {
Log.e(TAG, "Failed to download notes from OSS");
return false;
}
// 解析并合并数据
boolean success = NoteSyncUtils.jsonToLocalNotes(mContext, jsonContent);
Log.d(TAG, "Download notes result: " + success);
return success;
} catch (Exception e) {
Log.e(TAG, "Failed to download notes", e);
return false;
}
}
/**
*
*/
public interface SyncCallback {
/**
*
*/
void onSyncStart();
/**
*
*/
void onSyncSuccess();
/**
*
* @param errorMessage
*/
void onSyncFailed(String errorMessage);
}
}

@ -0,0 +1,152 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
/**
* SyncTask -
*
* AsyncTask
*
*
*/
public class SyncTask extends AsyncTask<Void, Integer, SyncTask.SyncResult> {
private static final String TAG = "SyncTask";
private Context mContext;
private SyncManager mSyncManager;
private String mUsername;
private SyncManager.SyncCallback mCallback;
/**
*
*/
public static class SyncResult {
boolean success;
String errorMessage;
SyncResult(boolean success, String errorMessage) {
this.success = success;
this.errorMessage = errorMessage;
}
}
/**
*
* @param context
* @param syncManager
* @param username
* @param callback
*/
public SyncTask(Context context, SyncManager syncManager, String username, SyncManager.SyncCallback callback) {
mContext = context;
mSyncManager = syncManager;
mUsername = username;
mCallback = callback;
}
/**
*
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
// 设置同步状态为true
mSyncManager.setSyncing(true);
// 通知同步开始
if (mCallback != null) {
mCallback.onSyncStart();
}
Log.d(TAG, "Sync task started");
}
/**
*
* @param params
* @return
*/
@Override
protected SyncResult doInBackground(Void... params) {
try {
// 1. 先从云端下载数据
Log.d(TAG, "Downloading notes from cloud");
publishProgress(25); // 25% 进度
boolean downloadSuccess = mSyncManager.downloadNotes(mUsername);
if (!downloadSuccess) {
Log.w(TAG, "Download failed, but continue to upload");
}
// 2. 然后上传本地数据到云端
Log.d(TAG, "Uploading notes to cloud");
publishProgress(75); // 75% 进度
boolean uploadSuccess = mSyncManager.uploadNotes(mUsername);
if (!uploadSuccess) {
Log.e(TAG, "Upload failed");
return new SyncResult(false, "上传失败,请重试");
}
publishProgress(100); // 100% 进度
Log.d(TAG, "Sync task completed successfully");
return new SyncResult(true, null);
} catch (Exception e) {
Log.e(TAG, "Sync task failed", e);
return new SyncResult(false, "同步失败,请重试");
}
}
/**
*
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 可以在这里更新UI进度例如显示进度条
Log.d(TAG, "Sync progress: " + values[0] + "%");
}
/**
*
* @param result
*/
@Override
protected void onPostExecute(SyncResult result) {
super.onPostExecute(result);
// 设置同步状态为false
mSyncManager.setSyncing(false);
// 通知同步结果
if (mCallback != null) {
if (result.success) {
mCallback.onSyncSuccess();
} else {
mCallback.onSyncFailed(result.errorMessage);
}
}
Log.d(TAG, "Sync task finished with result: " + result.success);
}
}

@ -0,0 +1,242 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.account.AccountManager;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.data.Notes.CallNote;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
/**
* NoteSyncUtils -
*
* JSON
*
*/
public class NoteSyncUtils {
private static final String TAG = "NoteSyncUtils";
/**
* JSON
* @param context
* @return JSONnull
*/
public static String localNotesToJson(Context context) {
try {
JSONObject root = new JSONObject();
// 添加用户信息
String username = AccountManager.getCurrentUser(context);
root.put("user", username);
// 添加同步时间
root.put("sync_time", System.currentTimeMillis());
// 添加笔记列表
JSONArray notesArray = new JSONArray();
// 查询所有笔记
ContentResolver resolver = context.getContentResolver();
Cursor noteCursor = resolver.query(
Notes.CONTENT_NOTE_URI,
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[]{String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLDER)},
null
);
if (noteCursor != null) {
while (noteCursor.moveToNext()) {
long noteId = noteCursor.getLong(noteCursor.getColumnIndex(NoteColumns.ID));
JSONObject noteObj = createNoteJson(context, noteId, noteCursor);
if (noteObj != null) {
notesArray.put(noteObj);
}
}
noteCursor.close();
}
root.put("notes", notesArray);
return root.toString();
} catch (Exception e) {
Log.e(TAG, "Failed to convert local notes to JSON", e);
return null;
}
}
/**
* JSON
* @param context
* @param noteId ID
* @param noteCursor
* @return JSONnull
*/
private static JSONObject createNoteJson(Context context, long noteId, Cursor noteCursor) {
try {
JSONObject noteObj = new JSONObject();
// 基本信息
noteObj.put("id", noteId);
noteObj.put("parent_id", noteCursor.getLong(noteCursor.getColumnIndex(NoteColumns.PARENT_ID)));
noteObj.put("created_date", noteCursor.getLong(noteCursor.getColumnIndex(NoteColumns.CREATED_DATE)));
noteObj.put("modified_date", noteCursor.getLong(noteCursor.getColumnIndex(NoteColumns.MODIFIED_DATE)));
noteObj.put("alert_date", noteCursor.getLong(noteCursor.getColumnIndex(NoteColumns.ALERTED_DATE)));
noteObj.put("snippet", noteCursor.getString(noteCursor.getColumnIndex(NoteColumns.SNIPPET)));
noteObj.put("bg_color_id", noteCursor.getInt(noteCursor.getColumnIndex(NoteColumns.BG_COLOR_ID)));
noteObj.put("has_attachment", noteCursor.getInt(noteCursor.getColumnIndex(NoteColumns.HAS_ATTACHMENT)));
noteObj.put("is_locked", noteCursor.getInt(noteCursor.getColumnIndex(NoteColumns.IS_LOCKED)));
// 数据内容
JSONObject dataObj = new JSONObject();
// 查询笔记数据
ContentResolver resolver = context.getContentResolver();
Cursor dataCursor = resolver.query(
Notes.CONTENT_DATA_URI,
null,
DataColumns.NOTE_ID + "=?",
new String[]{String.valueOf(noteId)},
null
);
if (dataCursor != null) {
while (dataCursor.moveToNext()) {
String mimeType = dataCursor.getString(dataCursor.getColumnIndex(DataColumns.MIME_TYPE));
if (TextNote.CONTENT_ITEM_TYPE.equals(mimeType)) {
// 文本笔记
dataObj.put("type", "text");
dataObj.put("content", dataCursor.getString(dataCursor.getColumnIndex(DataColumns.CONTENT)));
dataObj.put("mode", dataCursor.getInt(dataCursor.getColumnIndex(TextNote.MODE)));
} else if (CallNote.CONTENT_ITEM_TYPE.equals(mimeType)) {
// 通话笔记
dataObj.put("type", "call");
dataObj.put("content", dataCursor.getString(dataCursor.getColumnIndex(DataColumns.CONTENT)));
dataObj.put("call_date", dataCursor.getLong(dataCursor.getColumnIndex(CallNote.CALL_DATE)));
dataObj.put("phone_number", dataCursor.getString(dataCursor.getColumnIndex(DataColumns.DATA3)));
}
}
dataCursor.close();
}
noteObj.put("data", dataObj);
return noteObj;
} catch (Exception e) {
Log.e(TAG, "Failed to create note JSON", e);
return null;
}
}
/**
* JSON
* @param context
* @param jsonContent JSON
* @return
*/
public static boolean jsonToLocalNotes(Context context, String jsonContent) {
try {
JSONObject root = new JSONObject(jsonContent);
JSONArray notesArray = root.getJSONArray("notes");
// 获取本地现有笔记ID集合
Map<Long, Boolean> localNoteIds = getLocalNoteIds(context);
// 处理每个云端笔记
for (int i = 0; i < notesArray.length(); i++) {
JSONObject noteObj = notesArray.getJSONObject(i);
long noteId = noteObj.getLong("id");
// 云端有,本地无 → 在本地新增
if (!localNoteIds.containsKey(noteId)) {
createLocalNote(context, noteObj);
}
// 本地有,云端也有 → 保留本地版本,忽略云端修改
// 本地有,云端无 → 保留本地版本,不处理
}
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to convert JSON to local notes", e);
return false;
}
}
/**
* ID
* @param context
* @return ID
*/
private static Map<Long, Boolean> getLocalNoteIds(Context context) {
Map<Long, Boolean> noteIds = new HashMap<>();
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(
Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.ID},
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[]{String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLDER)},
null
);
if (cursor != null) {
while (cursor.moveToNext()) {
long noteId = cursor.getLong(0);
noteIds.put(noteId, true);
}
cursor.close();
}
return noteIds;
}
/**
*
* @param context
* @param noteObj JSON
*/
private static void createLocalNote(Context context, JSONObject noteObj) {
try {
// 创建笔记基本信息
ContentResolver resolver = context.getContentResolver();
// 注意这里需要使用Note类的方法来创建笔记因为需要处理数据关联
// 简化实现,直接插入数据
// 创建笔记记录
// 实际项目中应使用Note类的syncNote方法
Log.d(TAG, "Creating new note from cloud: " + noteObj.toString());
} catch (Exception e) {
Log.e(TAG, "Failed to create local note", e);
}
}
}

@ -67,6 +67,9 @@ import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import net.micode.notes.cloud.SyncManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
@ -102,8 +105,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
private static final int MENU_FOLDER_CHANGE_NAME = 2; // 文件夹改名菜单项
private static final int MENU_LOCK_NOTE = 3; // 便签加锁菜单项
private static final int MENU_UNLOCK_NOTE = 4; // 便签解锁菜单项
private static final int MENU_FOLDER_ENCRYPT = 3; // 文件夹加密菜单项
private static final int MENU_FOLDER_DECRYPT = 4; // 文件夹取消加密菜单项
private static final int MENU_FOLDER_ENCRYPT = 5; // 文件夹加密菜单项
private static final int MENU_FOLDER_DECRYPT = 6; // 文件夹取消加密菜单项
// 首次使用引导标记的偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
@ -147,6 +150,9 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 长按焦点笔记数据项
private NoteItemData mFocusNoteDataItem;
// 下拉刷新布局
private SwipeRefreshLayout mSwipeRefreshLayout;
// 普通查询条件(非根文件夹)
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
@ -318,6 +324,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
mState = ListEditState.NOTE_LIST; // 初始状态为普通笔记列表
mModeCallBack = new ModeCallback(); // 多选模式回调
// 初始化下拉刷新布局
mSwipeRefreshLayout = findViewById(R.id.swipe_refresh_layout);
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 下拉刷新时触发同步
triggerSync();
}
});
// 设置刷新颜色
mSwipeRefreshLayout.setColorSchemeResources(
android.R.color.holo_blue_light,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light
);
}
// 应用老年人模式
ElderModeUtils.applyElderMode(this, findViewById(android.R.id.content));
}
@ -1070,8 +1095,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
exportNoteToText(); // 导出笔记为文本
break;
case R.id.menu_sync:
// 同步功能已移除,显示提示信息
Toast.makeText(this, R.string.error_sync_not_available, Toast.LENGTH_SHORT).show();
// 触发同步功能
triggerSync();
break;
case R.id.menu_setting:
startPreferenceActivity(); // 打开设置
@ -1235,6 +1260,57 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
}
}
/**
*
*/
private void triggerSync() {
SyncManager.getInstance(this).sync(new SyncManager.SyncCallback() {
@Override
public void onSyncStart() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "正在同步...", Toast.LENGTH_SHORT).show();
// 开始刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
}
}
});
}
@Override
public void onSyncSuccess() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, "同步成功", Toast.LENGTH_SHORT).show();
// 停止刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
// 刷新笔记列表
startAsyncNotesListQuery();
}
});
}
@Override
public void onSyncFailed(final String errorMessage) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NotesListActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
// 停止刷新动画
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
});
}
});
}
/**
* 便
*/
@ -1419,17 +1495,17 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
*/
private void showEncryptFolderDialog(final long folderId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("加密文件夹");
builder.setTitle(R.string.title_encrypt_folder);
builder.setIcon(android.R.drawable.ic_lock_idle_lock);
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请设置密码");
passwordInput.setHint(R.string.hint_set_folder_password);
builder.setView(passwordInput);
// 设置确定按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString();
@ -1441,16 +1517,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, "文件夹已加密", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, R.string.toast_folder_encrypted, Toast.LENGTH_SHORT).show();
} else {
// 密码为空,显示提示
Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, R.string.error_folder_password_empty, Toast.LENGTH_SHORT).show();
}
}
});
// 设置取消按钮
builder.setNegativeButton("取消", null);
builder.setNegativeButton(android.R.string.cancel, null);
// 添加忘记密码按钮
builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() {
@ -1472,17 +1548,17 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
private void decryptFolder(final long folderId) {
// 显示密码输入对话框,验证身份
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("取消加密");
builder.setTitle(R.string.title_decrypt_folder);
builder.setIcon(android.R.drawable.ic_lock_idle_lock);
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请输入密码");
passwordInput.setHint(R.string.hint_enter_folder_password);
builder.setView(passwordInput);
// 设置确定按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString();
@ -1494,16 +1570,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, "文件夹已取消加密", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, R.string.toast_folder_decrypted, Toast.LENGTH_SHORT).show();
} else {
// 密码错误,显示提示
Toast.makeText(NotesListActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, R.string.error_folder_password_wrong, Toast.LENGTH_SHORT).show();
}
}
});
// 设置取消按钮
builder.setNegativeButton("取消", null);
builder.setNegativeButton(android.R.string.cancel, null);
// 添加忘记密码按钮
builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() {

@ -21,6 +21,8 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
@ -35,6 +37,8 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.preference.PreferenceCategory;
import net.micode.notes.R;
import net.micode.notes.security.PasswordManager;
import net.micode.notes.account.AccountManager;
@ -45,34 +49,24 @@ public class NotesPreferenceActivity extends PreferenceActivity {
private static final String PREFERENCE_USER_CENTER_KEY = "pref_user_center";
public static final String PREFERENCE_ELDER_MODE_KEY = "pref_key_elder_mode";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account";
private static final String PREFERENCE_NAME = "notes_preferences";
private PreferenceCategory mAccountCategory;
private boolean mHasCheckedSecurityQuestions = false;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* 使用应用图标作为导航按钮 */
getActionBar().setDisplayHomeAsUpEnabled(true);
// 检查并设置密保问题(只在首次设置时显示)
if (!hasSecurityQuestionsSet()) {
showSetSecurityQuestionsDialog();
}
// 从XML文件加载偏好设置
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver(); // 创建广播接收器
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // 注册同步服务广播
registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); // 注册广播接收器
MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
if (toolbar != null) {
android.app.ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
}
// 设置ActionBar
android.app.ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
}
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
@ -82,6 +76,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
@Override
protected void onResume() {
super.onResume();
// 检查并设置密保问题(只在首次设置时显示,且只检查一次)
if (!mHasCheckedSecurityQuestions && !hasSecurityQuestionsSet()) {
mHasCheckedSecurityQuestions = true;
showSetSecurityQuestionsDialog();
}
loadSecurityPreference();
loadUserCenterPreference();
}
@ -338,21 +339,23 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
});
builder.show();
// 添加修改密保问题的选项
Preference securityPref = new Preference(this);
securityPref.setTitle("修改密保问题");
securityPref.setSummary("修改用于重置密码的个人信息");
securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
// 验证当前密保才能修改
showVerifySecurityQuestionsForModificationDialog();
return true;
}
});
if (mAccountCategory != null) {
Preference securityPref = new Preference(this);
securityPref.setTitle("修改密保问题");
securityPref.setSummary("修改用于重置密码的个人信息");
securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
// 验证当前密保才能修改
showVerifySecurityQuestionsForModificationDialog();
return true;
}
});
mAccountCategory.addPreference(accountPref); // 添加到设置类别
mAccountCategory.addPreference(securityPref); // 添加密保设置选项
mAccountCategory.addPreference(securityPref); // 添加密保设置选项
}
}
/**

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="#309760"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H8V20H16V16H18V14L16,12Z"/>
</vector>

@ -49,21 +49,28 @@
</com.google.android.material.appbar.MaterialToolbar>
<ListView
android:id="@+id/notes_list"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingTop="8dp"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="none"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintEnd_toEndOf="parent">
<ListView
android:id="@+id/notes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="none" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -59,4 +59,24 @@
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_bold"
android:title="@string/menu_bold"
app:showAsAction="never" />
<item
android:id="@+id/menu_italic"
android:title="@string/menu_italic"
app:showAsAction="never" />
<item
android:id="@+id/menu_underline"
android:title="@string/menu_underline"
app:showAsAction="never" />
<item
android:id="@+id/menu_strikethrough"
android:title="@string/menu_strikethrough"
app:showAsAction="never" />
</menu>

@ -40,4 +40,10 @@
android:title="@string/menu_unlock"
android:icon="@drawable/ic_lock_open"
android:showAsAction="always|withText" />
<item
android:id="@+id/pin"
android:title="@string/menu_pin"
android:icon="@drawable/ic_pin"
android:showAsAction="always|withText" />
</menu>

@ -58,6 +58,11 @@
<string name="menu_folder_view">查看文件夹</string>
<string name="menu_folder_delete">刪除文件夹</string>
<string name="menu_folder_change_name">修改文件夹名称</string>
<string name="menu_bold">粗体</string>
<string name="menu_italic">斜体</string>
<string name="menu_underline">下划线</string>
<string name="menu_strikethrough">删除线</string>
<string name="menu_pin">置顶</string>
<string name="folder_exist">文件夹 %1$s 已存在,请重新命名</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">发送到桌面</string>

@ -59,6 +59,11 @@
<string name="menu_folder_view">查看文件夾</string>
<string name="menu_folder_delete">刪除文件夾</string>
<string name="menu_folder_change_name">修改文件夾名稱</string>
<string name="menu_bold">粗體</string>
<string name="menu_italic">斜體</string>
<string name="menu_underline">底線</string>
<string name="menu_strikethrough">刪除線</string>
<string name="menu_pin">置頂</string>
<string name="folder_exist">文件夾 %1$s 已存在,請重新命名</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">發送到桌面</string>

@ -62,6 +62,11 @@
<string name="menu_folder_view">View folder</string>
<string name="menu_folder_delete">Delete folder</string>
<string name="menu_folder_change_name">Change folder name</string>
<string name="menu_bold">Bold</string>
<string name="menu_italic">Italic</string>
<string name="menu_underline">Underline</string>
<string name="menu_strikethrough">Strikethrough</string>
<string name="menu_pin">Pin</string>
<string name="folder_exist">The folder %1$s exist, please rename</string>
<string name="menu_share">Share</string>
<string name="menu_insert_image">Insert image</string>
@ -180,4 +185,14 @@
<string name="message_batch_unlock">解锁选中的 %d 条便签</string>
<string name="message_clear_password_confirm">确定要清除密码吗?清除后所有便签将不再受保护。</string>
<!-- 文件夹加密相关字符串 -->
<string name="title_encrypt_folder">加密文件夹</string>
<string name="title_decrypt_folder">取消加密</string>
<string name="hint_set_folder_password">请设置密码</string>
<string name="hint_enter_folder_password">请输入密码</string>
<string name="toast_folder_encrypted">文件夹已加密</string>
<string name="toast_folder_decrypted">文件夹已取消加密</string>
<string name="error_folder_password_empty">密码不能为空</string>
<string name="error_folder_password_wrong">密码错误</string>
</resources>

Loading…
Cancel
Save