Compare commits

..

No commits in common. 'master' and 'ranhao_branch' have entirely different histories.

@ -1,63 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
/**
* OSSConfig - OSS
*
*
*
*
* 1. https://oss.console.aliyun.com/
* 2. OSS Bucket
* 3. "概览"Endpoint
* 4. "AccessKey管理"AccessKey
*
*
* - AccessKey
* - 使RAM
* - 使STS
*/
public class OSSConfig {
// OSS服务端点 - 根据您的Bucket所在区域填写
// 示例华东1杭州https://oss-cn-hangzhou.aliyuncs.com
// 示例华北2北京https://oss-cn-beijing.aliyuncs.com
// 示例华南1深圳https://oss-cn-shenzhen.aliyuncs.com
public static final String OSS_ENDPOINT = "https://oss-cn-wuhan-lr.aliyuncs.com";
// 阿里云AccessKey ID - 在阿里云控制台的"AccessKey管理"中创建
public static final String OSS_ACCESS_KEY_ID = "LTAI5tAiNrYEtYykvN9xNn3w";
// 阿里云AccessKey Secret - 在阿里云控制台的"AccessKey管理"中创建
public static final String OSS_ACCESS_KEY_SECRET = "JnRHqdTMBIoaONvEPNB8RyypZPADaM";
// OSS存储桶名称 - 在阿里云OSS控制台创建Bucket时指定的名称
public static final String OSS_BUCKET_NAME = "mini-notes";
// OSS文件存储路径前缀 - 所有便签数据将存储在此路径下
public static final String OSS_FILE_PREFIX = "notes/";
// OSS连接超时时间毫秒
public static final int OSS_CONNECTION_TIMEOUT = 300000;
// OSS Socket超时时间毫秒
public static final int OSS_SOCKET_TIMEOUT = 300000;
// OSS最大重试次数
public static final int OSS_MAX_RETRY_COUNT = 30;
}

@ -1,608 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* OSSManager - OSS
*
* OSS
* OSS
*
*
* 使HTTPOSS API
*/
public class OSSManager {
private static final String TAG = "OSSManager";
private Context mContext;
private String mAccessKeyId;
private String mAccessKeySecret;
private String mBucketName;
private String mFilePrefix;
/**
*
* @param context
*/
public OSSManager(Context context) {
mContext = context.getApplicationContext();
loadOSSConfig();
}
/**
* OSS
*/
private void loadOSSConfig() {
mAccessKeyId = OSSConfig.OSS_ACCESS_KEY_ID;
mAccessKeySecret = OSSConfig.OSS_ACCESS_KEY_SECRET;
mBucketName = OSSConfig.OSS_BUCKET_NAME;
mFilePrefix = OSSConfig.OSS_FILE_PREFIX;
Log.d(TAG, "OSS config loaded - Bucket: " + mBucketName);
}
/**
*
* @param username
* @return
*/
public String getFilePath(String username) {
return mFilePrefix + "notes_" + username + ".json";
}
/**
* OSS URL
* @param filePath
* @return URL
*/
private String buildOSSUrl(String filePath) {
try {
// 只对路径中的文件名部分进行编码,保留路径分隔符
String[] pathParts = filePath.split("/");
StringBuilder encodedPath = new StringBuilder();
for (int i = 0; i < pathParts.length; i++) {
if (i > 0) {
encodedPath.append("/");
}
encodedPath.append(URLEncoder.encode(pathParts[i], "UTF-8").replace("+", "%20"));
}
String baseUrl = "https://" + mBucketName + ".oss-cn-wuhan-lr.aliyuncs.com";
return baseUrl + "/" + encodedPath.toString();
} catch (Exception e) {
Log.e(TAG, "Failed to build OSS URL", e);
return "";
}
}
/**
* OSS
* @param filePath
* @param content
* @return
*/
public boolean uploadFile(String filePath, String content) {
HttpURLConnection connection = null;
OutputStream outputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
Log.d(TAG, "Building URL for upload: " + filePath);
Log.d(TAG, "Generated URL: " + urlStr);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("PUT");
connection.setDoOutput(true);
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
byte[] data = content.getBytes("UTF-8");
String contentMD5 = "";
String contentType = "application/json; charset=utf-8";
String date = getGMTDate();
connection.setRequestProperty("Content-Length", String.valueOf(data.length));
connection.setRequestProperty("Content-Type", contentType);
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("PUT", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Upload URL: " + urlStr);
Log.d(TAG, "CanonicalizedResource: " + canonicalizedResource);
Log.d(TAG, "Date: " + date);
Log.d(TAG, "Signature: " + signature);
Log.d(TAG, "Content length: " + data.length);
// 尝试建立连接
Log.d(TAG, "Connecting to OSS...");
connection.connect();
// 写入数据
Log.d(TAG, "Writing data to OSS...");
outputStream = connection.getOutputStream();
outputStream.write(data);
outputStream.flush();
Log.d(TAG, "Data written successfully");
// 获取响应
Log.d(TAG, "Getting response from OSS...");
int responseCode = connection.getResponseCode();
Log.d(TAG, "Upload response code: " + responseCode);
// 获取响应消息
String responseMessage = connection.getResponseMessage();
Log.d(TAG, "Upload response message: " + responseMessage);
if (responseCode == 200 || responseCode == 201) {
Log.d(TAG, "File uploaded successfully: " + filePath);
return true;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Upload error response: " + errorResponse);
} else {
Log.e(TAG, "No error stream available for response code: " + responseCode);
// 尝试获取响应头信息
Log.e(TAG, "Response headers:");
for (int i = 0; ; i++) {
String headerName = connection.getHeaderFieldKey(i);
if (headerName == null) break;
String headerValue = connection.getHeaderField(i);
Log.e(TAG, headerName + ": " + headerValue);
}
}
Log.e(TAG, "Upload failed with response code: " + responseCode + ", message: " + responseMessage);
return false;
}
} catch (IOException e) {
Log.e(TAG, "IO error during upload", e);
return false;
} catch (Exception e) {
Log.e(TAG, "OSS error during upload", e);
return false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
* OSS
* @param filePath
* @return null
*/
public String downloadFile(String filePath) {
HttpURLConnection connection = null;
InputStream inputStream = null;
InputStream errorStream = null;
BufferedReader reader = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("GET", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Download URL: " + urlStr);
Log.d(TAG, "CanonicalizedResource: " + canonicalizedResource);
Log.d(TAG, "Date: " + date);
Log.d(TAG, "Signature: " + signature);
int responseCode = connection.getResponseCode();
Log.d(TAG, "Download response code: " + responseCode);
if (responseCode == 200) {
inputStream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
Log.d(TAG, "File downloaded successfully: " + filePath);
Log.d(TAG, "Content length: " + content.length());
return content.toString();
} else if (responseCode == 404) {
Log.d(TAG, "File does not exist in OSS: " + filePath);
return null;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Download error response: " + errorResponse);
}
Log.e(TAG, "Download failed with response code: " + responseCode);
return null;
}
} catch (Exception e) {
Log.e(TAG, "OSS error during download", e);
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
if (inputStream != null) {
inputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
*
* @param filePath
* @return
*/
public boolean doesFileExist(String filePath) {
HttpURLConnection connection = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName+ "/" + filePath;
String signature = generateSignature("HEAD", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
int responseCode = connection.getResponseCode();
boolean exists = (responseCode == 200);
Log.d(TAG, "File exists check: " + filePath + " = " + exists + " (response code: " + responseCode + ")");
if (!exists && responseCode != 404) {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Existence check error response: " + errorResponse);
}
}
return exists;
} catch (Exception e) {
Log.e(TAG, "OSS error during existence check", e);
return false;
} finally {
try {
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
* OSS
*/
public void release() {
Log.d(TAG, "OSS client released");
}
/**
* OSS
* @return
*/
public boolean isConfigValid() {
return !mAccessKeyId.equals("your_access_key_id_here") &&
!mAccessKeySecret.equals("your_access_key_secret_here") &&
!mBucketName.equals("your_bucket_name_here");
}
/**
* GMT
* @return GMT
*/
private String getGMTDate() {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat.format(new Date());
}
/**
* OSS
* @param method HTTP
* @param contentMD5 Content-MD5
* @param contentType Content-Type
* @param date Date
* @param canonicalizedResource
* @return
*/
private String generateSignature(String method, String contentMD5, String contentType, String date, String canonicalizedResource) {
try {
String stringToSign = method + "\n" +
(contentMD5 != null ? contentMD5 : "") + "\n" +
(contentType != null ? contentType : "") + "\n" +
date + "\n" +
canonicalizedResource;
Log.d(TAG, "String to sign: \"" + stringToSign.replace("\n", "\\n") + "\"");
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKeySpec = new SecretKeySpec(mAccessKeySecret.getBytes("UTF-8"), "HmacSHA1");
mac.init(secretKeySpec);
byte[] signatureBytes = mac.doFinal(stringToSign.getBytes("UTF-8"));
String signature = new String(android.util.Base64.encode(signatureBytes, android.util.Base64.NO_WRAP));
return signature;
} catch (Exception e) {
Log.e(TAG, "Failed to generate signature", e);
return "";
}
}
/**
*
* @param inputStream
* @return
*/
private String readStream(InputStream inputStream) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} catch (Exception e) {
Log.e(TAG, "Failed to read stream", e);
return "";
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
Log.e(TAG, "Error closing reader", e);
}
}
}
/**
* OSS
* @param filePath
* @param data
* @param contentType
* @return
*/
public boolean uploadBinaryFile(String filePath, byte[] data, String contentType) {
HttpURLConnection connection = null;
OutputStream outputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("PUT");
connection.setDoOutput(true);
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
connection.setRequestProperty("Content-Length", String.valueOf(data.length));
connection.setRequestProperty("Content-Type", contentType);
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName + "/" + filePath;
String signature = generateSignature("PUT", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Upload binary file URL: " + urlStr);
Log.d(TAG, "Content length: " + data.length);
connection.connect();
outputStream = connection.getOutputStream();
outputStream.write(data);
outputStream.flush();
int responseCode = connection.getResponseCode();
Log.d(TAG, "Upload binary file response code: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
Log.d(TAG, "Binary file uploaded successfully: " + filePath);
return true;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Upload binary file error response: " + errorResponse);
}
Log.e(TAG, "Upload binary file failed with response code: " + responseCode);
return false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to upload binary file", e);
return false;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
/**
* OSS
* @param filePath
* @return null
*/
public byte[] downloadBinaryFile(String filePath) {
HttpURLConnection connection = null;
InputStream inputStream = null;
InputStream errorStream = null;
try {
String urlStr = buildOSSUrl(filePath);
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(OSSConfig.OSS_CONNECTION_TIMEOUT);
connection.setReadTimeout(OSSConfig.OSS_SOCKET_TIMEOUT);
String date = getGMTDate();
String contentMD5 = "";
String contentType = "";
connection.setRequestProperty("Date", date);
String canonicalizedResource = "/" + mBucketName + "/" + filePath;
String signature = generateSignature("GET", contentMD5, contentType, date, canonicalizedResource);
connection.setRequestProperty("Authorization", "OSS " + mAccessKeyId + ":" + signature);
Log.d(TAG, "Download binary file URL: " + urlStr);
int responseCode = connection.getResponseCode();
Log.d(TAG, "Download binary file response code: " + responseCode);
if (responseCode == 200) {
inputStream = connection.getInputStream();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] data = baos.toByteArray();
Log.d(TAG, "Binary file downloaded successfully: " + filePath + ", size: " + data.length);
return data;
} else if (responseCode == 404) {
Log.d(TAG, "Binary file does not exist in OSS: " + filePath);
return null;
} else {
errorStream = connection.getErrorStream();
if (errorStream != null) {
String errorResponse = readStream(errorStream);
Log.e(TAG, "Download binary file error response: " + errorResponse);
}
Log.e(TAG, "Download binary file failed with response code: " + responseCode);
return null;
}
} catch (Exception e) {
Log.e(TAG, "Failed to download binary file", e);
return null;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (errorStream != null) {
errorStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error closing connection", e);
}
}
}
}

@ -1,132 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.app.AlertDialog;
import android.content.DialogInterface;
/**
* SyncDialogManager -
*
*
*
*
* UI
*/
public class SyncDialogManager {
private static final String TAG = "SyncDialogManager";
/**
*
*/
public interface SyncOptionListener {
/**
*
*/
void onUploadSelected();
/**
*
*/
void onDownloadSelected();
/**
*
*/
void onCanceled();
}
/**
*
*/
public interface ConfirmListener {
/**
*
*/
void onConfirm();
/**
*
*/
void onCancel();
}
/**
*
* @param context
* @param listener
*/
public static void showSyncOptionsDialog(Context context, final SyncOptionListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("同步操作选择")
.setMessage("请选择要执行的同步操作")
.setNegativeButton("下载", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onDownloadSelected();
}
}
})
.setPositiveButton("上传", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onUploadSelected();
}
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (listener != null) {
listener.onCanceled();
}
}
})
.show();
}
/**
*
* @param context
* @param listener
*/
public static void showUploadConfirmDialog(Context context, final ConfirmListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("上传确认")
.setMessage("上传会覆盖云端内容,确定要继续吗?")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onCancel();
}
}
})
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listener != null) {
listener.onConfirm();
}
}
})
.show();
}
}

@ -1,494 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.cloud;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.account.AccountManager;
import net.micode.notes.data.AttachmentManager;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.NoteSyncUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
/**
* 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;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
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;
}
// 解析JSON获取附件列表
JSONObject root = new JSONObject(jsonContent);
JSONArray notesArray = root.optJSONArray("notes");
if (notesArray != null) {
for (int i = 0; i < notesArray.length(); i++) {
JSONObject noteObj = notesArray.optJSONObject(i);
if (noteObj != null) {
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null) {
for (int j = 0; j < attachmentsArray.length(); j++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(j);
if (attachmentObj != null) {
String filePath = attachmentObj.optString("file_path", "");
if (!filePath.isEmpty()) {
// 上传附件文件到OSS
uploadAttachmentFile(username, filePath);
}
}
}
}
}
}
}
// 上传JSON数据到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;
}
}
/**
* OSS
* @param username
* @param localFilePath
* @return
*/
private boolean uploadAttachmentFile(String username, String localFilePath) {
try {
File file = new File(localFilePath);
if (!file.exists()) {
Log.w(TAG, "Attachment file does not exist: " + localFilePath);
return false;
}
// 读取文件内容
java.io.FileInputStream fis = new java.io.FileInputStream(file);
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
fis.close();
byte[] data = baos.toByteArray();
// 构建OSS文件路径
String fileName = file.getName();
String ossFilePath = OSSConfig.OSS_FILE_PREFIX + "attachments/" + username + "/" + fileName;
// 上传到OSS
String contentType = "image/jpeg"; // 默认图片类型
if (fileName.toLowerCase().endsWith(".png")) {
contentType = "image/png";
} else if (fileName.toLowerCase().endsWith(".gif")) {
contentType = "image/gif";
}
boolean success = mOssManager.uploadBinaryFile(ossFilePath, data, contentType);
if (success) {
Log.d(TAG, "Attachment uploaded successfully: " + ossFilePath);
} else {
Log.e(TAG, "Failed to upload attachment: " + ossFilePath);
}
return success;
} catch (Exception e) {
Log.e(TAG, "Failed to upload attachment file: " + localFilePath, 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;
}
// 解析JSON获取附件列表
JSONObject root = new JSONObject(jsonContent);
JSONArray notesArray = root.optJSONArray("notes");
if (notesArray != null) {
for (int i = 0; i < notesArray.length(); i++) {
JSONObject noteObj = notesArray.optJSONObject(i);
if (noteObj != null) {
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null) {
for (int j = 0; j < attachmentsArray.length(); j++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(j);
if (attachmentObj != null) {
String filePath2 = attachmentObj.optString("file_path", "");
if (!filePath2.isEmpty()) {
// 下载附件文件从OSS
downloadAttachmentFile(username, filePath2);
}
}
}
}
}
}
}
// 解析并合并数据
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;
}
}
/**
* OSS
* @param username
* @param originalFilePath
* @return
*/
private boolean downloadAttachmentFile(String username, String originalFilePath) {
try {
// 获取文件名
File originalFile = new File(originalFilePath);
String fileName = originalFile.getName();
// 构建OSS文件路径
String ossFilePath = OSSConfig.OSS_FILE_PREFIX + "attachments/" + username + "/" + fileName;
// 从OSS下载文件
byte[] data = mOssManager.downloadBinaryFile(ossFilePath);
if (data == null) {
Log.w(TAG, "Attachment not found in OSS: " + ossFilePath);
return false;
}
// 获取附件存储目录
AttachmentManager attachmentManager = new AttachmentManager(mContext);
File storageDir = attachmentManager.getAttachmentStorageDir();
if (!storageDir.exists()) {
storageDir.mkdirs();
}
// 保存文件到本地
File destFile = new File(storageDir, fileName);
FileOutputStream fos = new FileOutputStream(destFile);
fos.write(data);
fos.close();
Log.d(TAG, "Attachment downloaded successfully: " + destFile.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to download attachment file: " + originalFilePath, e);
return false;
}
}
/**
*
*/
public interface SyncCallback {
/**
*
*/
void onSyncStart();
/**
*
*/
void onSyncSuccess();
/**
*
* @param errorMessage
*/
void onSyncFailed(String errorMessage);
}
/**
*
* @param callback
*/
public void triggerUpload(final SyncCallback callback) {
if (mIsSyncing) {
Log.d(TAG, "Upload already in progress");
if (callback != null) {
callback.onSyncFailed("上传已在进行中");
}
return;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
if (callback != null) {
callback.onSyncFailed("请先登录后再同步");
}
return;
}
final String username = AccountManager.getCurrentUser(mContext);
if (username.isEmpty()) {
Log.w(TAG, "Empty username");
if (callback != null) {
callback.onSyncFailed("用户名为空");
}
return;
}
// 设置同步状态
setSyncing(true);
// 通知同步开始
if (callback != null) {
callback.onSyncStart();
}
// 在后台执行上传操作
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean success = uploadNotes(username);
setSyncing(false);
if (callback != null) {
if (success) {
callback.onSyncSuccess();
} else {
callback.onSyncFailed("上传失败,请重试");
}
}
} catch (Exception e) {
Log.e(TAG, "Upload failed", e);
setSyncing(false);
if (callback != null) {
callback.onSyncFailed("上传失败:" + e.getMessage());
}
}
}
}).start();
}
/**
*
* @param callback
*/
public void triggerDownload(final SyncCallback callback) {
if (mIsSyncing) {
Log.d(TAG, "Download already in progress");
if (callback != null) {
callback.onSyncFailed("下载已在进行中");
}
return;
}
// 检查OSS配置是否有效
if (!mOssManager.isConfigValid()) {
Log.w(TAG, "OSS configuration is invalid");
if (callback != null) {
callback.onSyncFailed("OSS配置无效请检查配置文件");
}
return;
}
// 检查用户登录状态
if (!AccountManager.isUserLoggedIn(mContext)) {
Log.w(TAG, "User not logged in");
if (callback != null) {
callback.onSyncFailed("请先登录后再同步");
}
return;
}
final String username = AccountManager.getCurrentUser(mContext);
if (username.isEmpty()) {
Log.w(TAG, "Empty username");
if (callback != null) {
callback.onSyncFailed("用户名为空");
}
return;
}
// 设置同步状态
setSyncing(true);
// 通知同步开始
if (callback != null) {
callback.onSyncStart();
}
// 在后台执行下载操作
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean success = downloadNotes(username);
setSyncing(false);
if (callback != null) {
if (success) {
callback.onSyncSuccess();
} else {
callback.onSyncFailed("下载失败,请重试");
}
}
} catch (Exception e) {
Log.e(TAG, "Download failed", e);
setSyncing(false);
if (callback != null) {
callback.onSyncFailed("下载失败:" + e.getMessage());
}
}
}
}).start();
}
}

@ -1,152 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.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, "OSS操作失败请稍后重试");
}
}
/**
*
* @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);
}
}

@ -115,7 +115,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
NoteColumns.IS_LOCKED + " INTEGER NOT NULL DEFAULT 0" +
")";

@ -387,8 +387,7 @@ public class Note {
return null;
}
}
// 如果只有插入操作(没有更新操作),也返回成功
return ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
return null;
}
}
}

@ -32,7 +32,6 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class DataUtils {
@ -113,30 +112,12 @@ public class DataUtils {
return true;
}
// 先查询所有笔记的当前parent_id用于记录原始文件夹
HashMap<Long, Long> noteToParentMap = new HashMap<>();
for (long id : ids) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
new String[]{NoteColumns.PARENT_ID}, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
noteToParentMap.put(id, cursor.getLong(0));
}
cursor.close();
}
}
// 构建批量更新操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId); // 更新父文件夹ID
// 记录原始父文件夹ID如果查询到的话
Long originParentId = noteToParentMap.get(id);
if (originParentId != null) {
builder.withValue(NoteColumns.ORIGIN_PARENT_ID, originParentId);
}
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已本地修改
operationList.add(builder.build());
}

@ -1,359 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.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 net.micode.notes.data.AttachmentManager;
import net.micode.notes.model.Note;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
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);
// 附件信息
JSONArray attachmentsArray = new JSONArray();
AttachmentManager attachmentManager = new AttachmentManager(context);
List<AttachmentManager.Attachment> attachments = attachmentManager.getAttachmentsByNoteId(noteId);
for (AttachmentManager.Attachment attachment : attachments) {
JSONObject attachmentObj = new JSONObject();
attachmentObj.put("id", attachment.id);
attachmentObj.put("type", attachment.type);
attachmentObj.put("file_path", attachment.filePath);
attachmentObj.put("created_time", attachment.createdTime);
attachmentsArray.put(attachmentObj);
}
noteObj.put("attachments", attachmentsArray);
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 {
Log.d(TAG, "Creating new note from cloud: " + noteObj.toString());
// 获取笔记基本信息
long parentId = noteObj.optLong("parent_id", Notes.ID_ROOT_FOLDER);
long createdDate = noteObj.optLong("created_date", System.currentTimeMillis());
long modifiedDate = noteObj.optLong("modified_date", System.currentTimeMillis());
long alertDate = noteObj.optLong("alert_date", 0);
String snippet = noteObj.optString("snippet", "");
int bgColorId = noteObj.optInt("bg_color_id", 0);
int hasAttachment = noteObj.optInt("has_attachment", 0);
int isLocked = noteObj.optInt("is_locked", 0);
// 获取笔记内容
JSONObject dataObj = noteObj.optJSONObject("data");
String noteType = dataObj.optString("type", "text");
// 创建新笔记
long noteId = Note.getNewNoteId(context, parentId);
Log.d(TAG, "Created new note with id: " + noteId);
// 创建Note对象
Note note = new Note();
// 设置笔记基本信息
note.setNoteValue(NoteColumns.CREATED_DATE, String.valueOf(createdDate));
note.setNoteValue(NoteColumns.MODIFIED_DATE, String.valueOf(modifiedDate));
note.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(alertDate));
note.setNoteValue(NoteColumns.SNIPPET, snippet);
note.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(bgColorId));
note.setNoteValue(NoteColumns.HAS_ATTACHMENT, String.valueOf(hasAttachment));
note.setNoteValue(NoteColumns.IS_LOCKED, String.valueOf(isLocked));
// 设置笔记内容
if ("text".equals(noteType)) {
// 文本笔记
String content = dataObj.optString("content", "");
int mode = dataObj.optInt("mode", 0);
note.setTextData(TextNote.CONTENT, content);
note.setTextData(TextNote.MODE, String.valueOf(mode));
Log.d(TAG, "Set text note content: " + content);
} else if ("call".equals(noteType)) {
// 通话笔记
String content = dataObj.optString("content", "");
long callDate = dataObj.optLong("call_date", 0);
String phoneNumber = dataObj.optString("phone_number", "");
note.setCallData(CallNote.CONTENT, content);
note.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
note.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
Log.d(TAG, "Set call note content: " + content + ", phone: " + phoneNumber);
}
// 同步笔记到数据库
boolean success = note.syncNote(context, noteId);
Log.d(TAG, "Sync note result: " + success);
if (success) {
Log.d(TAG, "Note created successfully from cloud: " + noteId);
// 恢复附件信息
JSONArray attachmentsArray = noteObj.optJSONArray("attachments");
if (attachmentsArray != null && attachmentsArray.length() > 0) {
Log.d(TAG, "Restoring " + attachmentsArray.length() + " attachments for note: " + noteId);
for (int i = 0; i < attachmentsArray.length(); i++) {
JSONObject attachmentObj = attachmentsArray.optJSONObject(i);
if (attachmentObj != null) {
// 创建附件记录附件文件需要从OSS下载
String filePath = attachmentObj.optString("file_path", "");
int type = attachmentObj.optInt("type", 0);
long createdTime = attachmentObj.optLong("created_time", System.currentTimeMillis());
// 注意这里只记录附件元数据实际的文件下载由SyncManager处理
// 文件下载后会更新filePath
createAttachmentRecord(context, noteId, type, filePath, createdTime);
}
}
}
} else {
Log.e(TAG, "Failed to sync note to local database: " + noteId);
}
} catch (Exception e) {
Log.e(TAG, "Failed to create local note", e);
}
}
/**
*
* @param context
* @param noteId ID
* @param type
* @param filePath
* @param createdTime
*/
private static void createAttachmentRecord(Context context, long noteId, int type, String filePath, long createdTime) {
try {
android.content.ContentValues values = new android.content.ContentValues();
values.put(Notes.AttachmentColumns.NOTE_ID, noteId);
values.put(Notes.AttachmentColumns.TYPE, type);
values.put(Notes.AttachmentColumns.FILE_PATH, filePath);
values.put(Notes.AttachmentColumns.CREATED_TIME, createdTime);
android.net.Uri uri = context.getContentResolver().insert(Notes.CONTENT_ATTACHMENT_URI, values);
if (uri != null) {
Log.d(TAG, "Created attachment record: " + uri + " for note: " + noteId);
} else {
Log.e(TAG, "Failed to create attachment record for note: " + noteId);
}
} catch (Exception e) {
Log.e(TAG, "Failed to create attachment record", e);
}
}
}

@ -191,9 +191,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private static final int REQUEST_CAMERA_PREVIEW = 1005;
private String mCurrentPhotoPath; // 相机拍照临时文件路径
// 密码验证标志
private boolean mPasswordVerified = false; // 标记密码是否已验证通过
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -361,11 +358,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
if (TextUtils.equals(Intent.ACTION_VIEW, action)) {
// 检查便签是否加锁
if (isNoteLocked()) {
// 便签加锁需要验证密码此时不设置mPasswordVerified标志
showPasswordDialogForLockedNote();
} else {
// 未加锁便签,标记密码已验证(无需验证)
mPasswordVerified = true;
// 隐藏软键盘
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
@ -376,8 +370,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
loadAttachments();
}
} else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, action)) {
// 新建或编辑模式,标记密码已验证(新建便签无需密码)
mPasswordVerified = true;
// 显示软键盘
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
@ -420,7 +412,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
/**
* 便
*
*/
private void showPasswordDialogForLockedNote() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@ -441,8 +432,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
return;
}
if (PasswordManager.verifyPassword(NoteEditActivity.this, password)) {
// 密码验证通过设置标志位并初始化UI
mPasswordVerified = true;
// 密码验证通过初始化UI
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
@ -460,168 +450,15 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
finish();
}
});
// 添加"忘记密码"按钮,点击后通过密保问题验证重置密码
builder.setNeutralButton(R.string.btn_forgot_password, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User clicked forgot password button, showing security questions dialog");
showSecurityQuestionsForNoteUnlock();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
}
/**
* 便
* 便
*/
private void showSecurityQuestionsForNoteUnlock() {
// 检查是否设置了密保问题
if (!NotesPreferenceActivity.hasSecurityQuestionsSet(this)) {
Log.w(TAG, "Security questions not set, cannot reset password");
Toast.makeText(this, "请先在设置中设置密保问题", Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.d(TAG, "Showing security questions dialog for note unlock");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_forgot_password);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage("请回答密保问题以重置密码");
// 创建布局
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
// 创建姓名输入框
final EditText nameInput = new EditText(this);
nameInput.setHint("请输入姓名");
layout.addView(nameInput);
// 创建生日输入框
final EditText birthdayInput = new EditText(this);
birthdayInput.setHint("请输入生日 (YYYY-MM-DD)");
layout.addView(birthdayInput);
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = nameInput.getText().toString().trim();
String birthday = birthdayInput.getText().toString().trim();
Log.d(TAG, "Verifying security questions - name: " + name + ", birthday: " + birthday);
if (NotesPreferenceActivity.verifySecurityQuestions(NoteEditActivity.this, name, birthday)) {
Log.i(TAG, "Security questions verified successfully, showing reset password dialog");
// 验证成功,显示设置新密码对话框
showResetPasswordForNoteUnlock();
} else {
Log.w(TAG, "Security questions verification failed");
Toast.makeText(NoteEditActivity.this, "密保问题回答错误", Toast.LENGTH_SHORT).show();
finish();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User cancelled security questions dialog");
finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
}
/**
* 便
* 便
*/
private void showResetPasswordForNoteUnlock() {
Log.d(TAG, "Showing reset password dialog for note unlock");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("设置新密码");
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setMessage("验证成功,请设置新密码");
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请输入新密码");
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
layout.addView(passwordInput);
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newPassword = passwordInput.getText().toString().trim();
if (newPassword.isEmpty()) {
Log.w(TAG, "New password is empty");
Toast.makeText(NoteEditActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.i(TAG, "Setting new password for note unlock");
// 设置新密码
if (PasswordManager.setPassword(NoteEditActivity.this, newPassword)) {
Log.i(TAG, "Password reset successfully, auto-unlocking note");
Toast.makeText(NoteEditActivity.this, "密码重置成功", Toast.LENGTH_SHORT).show();
// 自动解锁并显示便签
mPasswordVerified = true;
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
initNoteScreen();
loadAttachments();
} else {
Log.e(TAG, "Failed to set new password");
Toast.makeText(NoteEditActivity.this, "密码设置失败", Toast.LENGTH_SHORT).show();
finish();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User cancelled reset password dialog");
finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.show();
builder.show();
builder.setCancelable(false);
}
@Override
protected void onResume() {
super.onResume();
// 只有当mWorkingNote已经初始化完成且密码已验证(或未加锁)时才初始化界面
if (mWorkingNote != null && mPasswordVerified) {
// 只有当mWorkingNote已经初始化完成时才初始化界面
if (mWorkingNote != null) {
initNoteScreen(); // 初始化笔记界面(已包含老年人模式应用)
}
}
@ -700,9 +537,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
/**
* IDID
* ID
* 便
*/
if (mPasswordVerified && !mWorkingNote.existInDatabase()) {
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID
@ -799,70 +635,13 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
// 应用老年人模式
ElderModeUtils.applyElderMode(this, findViewById(android.R.id.content));
// 初始化格式化工具栏按钮
initFormatToolbar();
}
/**
*
*/
private void initFormatToolbar() {
// 字体大小按钮
View btnFontSize = findViewById(R.id.btn_font_size);
if (btnFontSize != null) {
btnFontSize.setOnClickListener(v -> {
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
});
}
// 粗体按钮
View btnBold = findViewById(R.id.btn_bold);
if (btnBold != null) {
btnBold.setOnClickListener(v -> applyRichTextStyle(android.graphics.Typeface.BOLD));
}
// 斜体按钮
View btnItalic = findViewById(R.id.btn_italic);
if (btnItalic != null) {
btnItalic.setOnClickListener(v -> applyRichTextStyle(android.graphics.Typeface.ITALIC));
}
// 下划线按钮
View btnUnderline = findViewById(R.id.btn_underline);
if (btnUnderline != null) {
btnUnderline.setOnClickListener(v -> applyUnderlineStyle());
}
// 删除线按钮
View btnStrikethrough = findViewById(R.id.btn_strikethrough);
if (btnStrikethrough != null) {
btnStrikethrough.setOnClickListener(v -> applyStrikethroughStyle());
}
// 列表模式按钮
View btnListMode = findViewById(R.id.btn_list_mode);
if (btnListMode != null) {
btnListMode.setOnClickListener(v -> {
// 切换列表模式/普通模式
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
});
}
// 插入图片按钮
View btnInsertImage = findViewById(R.id.btn_insert_image);
if (btnInsertImage != null) {
btnInsertImage.setOnClickListener(v -> handleInsertImage());
}
}
@Override
protected void onPause() {
super.onPause();
// 只有在密码已验证通过时才保存笔记,防止加锁便签在密码错误时内容被覆盖
if (mPasswordVerified && saveNote()) {
// 保存笔记
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState(); // 清除设置状态
@ -1003,7 +782,12 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
getMenuInflater().inflate(R.menu.note_edit, menu); // 普通笔记菜单
}
// 列表模式已移至工具栏,不再在菜单中设置标题
// 设置列表模式/普通模式菜单项标题
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
// 根据是否有闹钟提醒显示/隐藏相关菜单项
if (mWorkingNote.hasClockAlert()) {
@ -1041,7 +825,18 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
// 字体大小、列表模式、插入图片已移至工具栏
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE); // 显示字体大小选择器
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
// 切换列表模式/普通模式
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_insert_image:
handleInsertImage(); // 插入图片
break;
case R.id.menu_share:
getWorkingText(); // 获取工作文本
sendTo(this, mWorkingNote.getContent()); // 分享笔记
@ -1055,7 +850,18 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false); // 删除提醒
break;
// 粗体、斜体、下划线、删除线、字体大小已移至工具栏
case R.id.menu_bold:
applyRichTextStyle(android.graphics.Typeface.BOLD);
break;
case R.id.menu_italic:
applyRichTextStyle(android.graphics.Typeface.ITALIC);
break;
case R.id.menu_underline:
applyUnderlineStyle();
break;
case R.id.menu_strikethrough:
applyStrikethroughStyle();
break;
default:
break;
}
@ -1113,9 +919,17 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
Log.d(TAG, "Wrong note id, should not happen");
}
// 将笔记移动到回收站,而不是直接删除
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
// 根据是否启用同步模式执行不同的删除操作
if (!isSyncMode()) {
// 普通模式:直接删除
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
// 同步模式:移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true); // 标记为已删除

@ -67,10 +67,6 @@ 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.cloud.SyncDialogManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
@ -106,8 +102,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 = 5; // 文件夹加密菜单项
private static final int MENU_FOLDER_DECRYPT = 6; // 文件夹取消加密菜单项
private static final int MENU_FOLDER_ENCRYPT = 3; // 文件夹加密菜单项
private static final int MENU_FOLDER_DECRYPT = 4; // 文件夹取消加密菜单项
// 首次使用引导标记的偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
@ -118,8 +114,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
private enum ListEditState {
NOTE_LIST, // 普通笔记列表状态
SUB_FOLDER, // 子文件夹浏览状态
CALL_RECORD_FOLDER, // 通话记录文件夹浏览状态
SEARCH // 搜索状态
CALL_RECORD_FOLDER // 通话记录文件夹浏览状态
};
// 当前列表状态
@ -152,14 +147,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 长按焦点笔记数据项
private NoteItemData mFocusNoteDataItem;
// 下拉刷新布局
private SwipeRefreshLayout mSwipeRefreshLayout;
// 搜索相关变量
private String mSearchKey = ""; // 搜索关键词
private View mSearchContainer; // 搜索容器视图
private EditText mSearchEditText; // 搜索输入框
// 普通查询条件(非根文件夹)
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
@ -213,35 +200,19 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
if (itemData == null || itemData.getType() != Notes.TYPE_NOTE) {
return;
}
// 查询笔记的原始父文件夹ID
long originParentId = Notes.ID_ROOT_FOLDER; // 默认恢复到根文件夹
Cursor cursor = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, itemData.getId()),
new String[]{NoteColumns.ORIGIN_PARENT_ID}, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
originParentId = cursor.getLong(0);
// 如果原始文件夹ID无效或者是回收站则恢复到根文件夹
if (originParentId <= 0 || originParentId == Notes.ID_TRASH_FOLDER) {
originParentId = Notes.ID_ROOT_FOLDER;
}
}
cursor.close();
}
// 显示确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("恢复笔记");
builder.setMessage("确定要恢复这条笔记吗?");
builder.setIcon(android.R.drawable.ic_dialog_info);
final long targetFolderId = originParentId;
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 恢复笔记到原始文件夹
// 恢复笔记到根文件夹
HashSet<Long> ids = new HashSet<Long>();
ids.add(itemData.getId());
if (DataUtils.batchMoveToFolder(mContentResolver, ids, targetFolderId)) {
if (DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_ROOT_FOLDER)) {
Toast.makeText(NotesListActivity.this, "笔记已恢复", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NotesListActivity.this, "恢复失败", Toast.LENGTH_SHORT).show();
@ -347,25 +318,6 @@ 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() {
// 下拉刷新时只执行下载操作
executeDownload();
}
});
// 设置刷新颜色
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));
}
@ -598,11 +550,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 根据当前文件夹选择查询条件
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 修改排序规则先按类型降序系统文件夹在前再按alerted_date降序置顶便签在前最后按修改时间降序
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.ALERTED_DATE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
@ -678,19 +629,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取选中笔记的小部件属性
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
// 根据当前文件夹决定删除方式
if (mCurrentFolderId == Notes.ID_TRASH_FOLDER) {
// 在回收站中,彻底删除笔记
if (!DataUtils.batchDeleteNotes(mContentResolver, selectedIds)) {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// 不在回收站中,将笔记移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, selectedIds, Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
// 无论是否同步模式,都将笔记移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
return widgets;
}
@ -716,51 +658,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
*/
private void batchPinNotes() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
private int pinnedCount = 0;
private int unpinnedCount = 0;
@Override
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取选中笔记的小部件属性
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
// 批量更新笔记的alerted_date字段
// 批量更新笔记的alerted_date字段为一个很大的值,表示置顶
for (Long noteId : selectedIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.ALERTED_DATE, 9999999999999L); // 很大的值,表示置顶
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
try {
// 查询笔记当前的alerted_date值
Cursor cursor = getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
new String[]{NoteColumns.ALERTED_DATE},
null, null, null);
long currentAlertedDate = 0;
if (cursor != null && cursor.moveToFirst()) {
currentAlertedDate = cursor.getLong(0);
cursor.close();
}
ContentValues values = new ContentValues();
// 判断是否已经置顶alerted_date大于当前时间
if (currentAlertedDate > System.currentTimeMillis()) {
// 已经置顶,取消置顶
values.put(NoteColumns.ALERTED_DATE, 0); // 0表示未置顶
unpinnedCount++;
} else {
// 未置顶,设置置顶
values.put(NoteColumns.ALERTED_DATE, 9999999999999L); // 很大的值,表示置顶
pinnedCount++;
}
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
values, null, null);
} catch (Exception e) {
Log.e(TAG, "Error toggling pin status: " + noteId, e);
Log.e(TAG, "Error pinning note: " + noteId, e);
}
}
return widgets;
@ -777,16 +693,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
}
}
}
// 显示相应的提示信息
if (pinnedCount > 0 && unpinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已置顶" + pinnedCount + "条笔记,已取消置顶" + unpinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
} else if (pinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已置顶" + pinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
} else if (unpinnedCount > 0) {
Toast.makeText(NotesListActivity.this, "已取消置顶" + unpinnedCount + "条笔记", Toast.LENGTH_SHORT).show();
}
Toast.makeText(NotesListActivity.this, "已置顶" + mNotesListAdapter.getSelectedCount() + "条笔记", Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode(); // 结束多选模式
}
}.execute();
@ -995,9 +902,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
public void onBackPressed() {
// 处理返回键,根据当前状态返回上一级或退出
switch (mState) {
case SEARCH:
exitSearch();
break;
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
@ -1150,8 +1054,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else if (mState == ListEditState.SEARCH) {
// 搜索状态下不显示菜单,因为搜索框已经占据了工具栏
} else {
Log.e(TAG, "Wrong state:" + mState);
}
@ -1168,8 +1070,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
exportNoteToText(); // 导出笔记为文本
break;
case R.id.menu_sync:
// 触发同步功能
triggerSync();
// 同步功能已移除,显示提示信息
Toast.makeText(this, R.string.error_sync_not_available, Toast.LENGTH_SHORT).show();
break;
case R.id.menu_setting:
startPreferenceActivity(); // 打开设置
@ -1188,128 +1090,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
@Override
public boolean onSearchRequested() {
toggleToSearch();
startSearch(null, false, null /* appData */, false);
return true;
}
/**
*
*
*/
private void showSearch() {
if (mState != ListEditState.SEARCH) {
// 切换到搜索状态
mState = ListEditState.SEARCH;
// 隐藏新建笔记按钮
mAddNewNote.setVisibility(View.GONE);
// 隐藏标题栏,显示搜索容器
mTitleBar.setVisibility(View.GONE);
mSearchContainer = findViewById(R.id.search_container);
mSearchContainer.setVisibility(View.VISIBLE);
mSearchEditText = (EditText) mSearchContainer.findViewById(R.id.search_input);
// 设置搜索输入框监听器
mSearchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
mSearchKey = s.toString();
// 根据搜索关键字刷新列表
startSearchQuery();
}
});
// 监听搜索框软键盘的"完成"键
mSearchEditText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH) {
mSearchKey = mSearchEditText.getText().toString();
startSearchQuery();
return true;
}
return false;
});
mSearchEditText.requestFocus();
showSoftInput();
// 设置取消按钮点击监听器
Button cancelButton = (Button) mSearchContainer.findViewById(R.id.search_cancel);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
exitSearch();
}
});
}
}
/**
*
*/
private void startSearchQuery() {
// 只搜索SNIPPET列因为CONTENT列在DataColumns中
String selection = NoteColumns.SNIPPET + " LIKE ?";
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection,
new String[] {"%" + mSearchKey + "%"},
NoteColumns.MODIFIED_DATE + " DESC");
}
/**
*
* showSearch
*
*/
private void toggleToSearch() {
if (mState != ListEditState.SEARCH) {
// 如果当前不在根目录,先切换到根目录
if (mCurrentFolderId != Notes.ID_ROOT_FOLDER) {
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
}
showSearch();
} else {
if (!mSearchKey.equals(mSearchEditText.getText().toString())) {
startSearchQuery();
}
}
}
/**
* 退
*/
private void exitSearch() {
if (mState == ListEditState.SEARCH) {
// 切换回普通笔记列表状态
mState = ListEditState.NOTE_LIST;
// 清空搜索关键字
mSearchKey = "";
// 隐藏搜索容器,显示标题栏
if (mSearchContainer != null) {
mSearchContainer.setVisibility(View.GONE);
}
mTitleBar.setVisibility(View.VISIBLE);
mTitleBar.setText(R.string.app_name);
// 显示新建笔记按钮
mAddNewNote.setVisibility(View.VISIBLE);
// 隐藏软键盘
if (mSearchEditText != null) {
hideSoftInput(mSearchEditText);
}
// 刷新笔记列表
startAsyncNotesListQuery();
// 重新创建菜单,确保设置图标显示
invalidateOptionsMenu();
}
}
/**
*
*/
@ -1419,13 +1203,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
Log.e(TAG, "Wrong note type in CALL_RECORD_FOLDER");
}
break;
case SEARCH:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SEARCH");
}
break;
default:
break;
}
@ -1458,140 +1235,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
}
}
/**
*
*/
private void triggerSync() {
SyncDialogManager.showSyncOptionsDialog(this, new SyncDialogManager.SyncOptionListener() {
@Override
public void onUploadSelected() {
SyncDialogManager.showUploadConfirmDialog(NotesListActivity.this, new SyncDialogManager.ConfirmListener() {
@Override
public void onConfirm() {
executeUpload();
}
@Override
public void onCancel() {
// 取消操作,返回选择对话框
}
});
}
@Override
public void onDownloadSelected() {
executeDownload();
}
@Override
public void onCanceled() {
// 取消操作
}
});
}
/**
*
*/
private void executeUpload() {
SyncManager.getInstance(this).triggerUpload(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);
}
}
});
}
});
}
/**
*
*/
private void executeDownload() {
SyncManager.getInstance(this).triggerDownload(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);
}
}
});
}
});
}
/**
* 便
*/
@ -1776,17 +1419,17 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
*/
private void showEncryptFolderDialog(final long folderId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_encrypt_folder);
builder.setTitle("加密文件夹");
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(R.string.hint_set_folder_password);
passwordInput.setHint("请设置密码");
builder.setView(passwordInput);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString();
@ -1798,16 +1441,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, R.string.toast_folder_encrypted, Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "文件夹已加密", Toast.LENGTH_SHORT).show();
} else {
// 密码为空,显示提示
Toast.makeText(NotesListActivity.this, R.string.error_folder_password_empty, Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNegativeButton("取消", null);
// 添加忘记密码按钮
builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() {
@ -1829,17 +1472,17 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
private void decryptFolder(final long folderId) {
// 显示密码输入对话框,验证身份
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_decrypt_folder);
builder.setTitle("取消加密");
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(R.string.hint_enter_folder_password);
passwordInput.setHint("请输入密码");
builder.setView(passwordInput);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString();
@ -1851,16 +1494,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, R.string.toast_folder_decrypted, Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "文件夹已取消加密", Toast.LENGTH_SHORT).show();
} else {
// 密码错误,显示提示
Toast.makeText(NotesListActivity.this, R.string.error_folder_password_wrong, Toast.LENGTH_SHORT).show();
Toast.makeText(NotesListActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
}
}
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNegativeButton("取消", null);
// 添加忘记密码按钮
builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() {
@ -1899,12 +1542,9 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
/**
*
*
* @param folderId ID
*/
private void showFolderPasswordDialog(final long folderId) {
Log.d(TAG, "Showing folder password dialog for folderId: " + folderId);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("输入密码");
builder.setIcon(android.R.drawable.ic_lock_idle_lock);
@ -1919,15 +1559,11 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordInput.getText().toString().trim();
Log.d(TAG, "User entered password for folder: " + folderId);
String password = passwordInput.getText().toString();
if (checkFolderPassword(folderId, password)) {
Log.i(TAG, "Folder password verified successfully, entering folder: " + folderId);
// 密码正确,进入文件夹
enterEncryptedFolder(folderId);
} else {
Log.w(TAG, "Folder password verification failed for folder: " + folderId);
// 密码错误,显示提示
Toast.makeText(NotesListActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show();
// 重新显示密码输入对话框
@ -1939,37 +1575,23 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
// 设置取消按钮
builder.setNegativeButton("取消", null);
// 添加"忘记密码"按钮,点击后通过密保问题验证重置密码
builder.setNeutralButton(R.string.btn_forgot_password, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "User clicked forgot password button for folder: " + folderId);
// 显示通过密保问题重置密码的对话框
showResetPasswordWithSecurityQuestionsDialog(folderId);
}
});
// 显示对话框
builder.show();
}
/**
*
*
* @param folderId ID
*/
private void showResetPasswordWithSecurityQuestionsDialog(final long folderId) {
Log.d(TAG, "Showing reset password with security questions dialog for folder: " + folderId);
// 检查是否设置了密保问题
if (!NotesPreferenceActivity.hasSecurityQuestionsSet(this)) {
Log.w(TAG, "Security questions not set, cannot reset folder password");
Toast.makeText(this, "请先在设置中设置密保问题", Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_forgot_password);
builder.setTitle("忘记密码");
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage("请回答密保问题以重置密码");
@ -1991,20 +1613,15 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
builder.setView(layout);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = nameInput.getText().toString().trim();
String birthday = birthdayInput.getText().toString().trim();
Log.d(TAG, "Verifying security questions for folder password reset - name: " + name);
String name = nameInput.getText().toString();
String birthday = birthdayInput.getText().toString();
if (NotesPreferenceActivity.verifySecurityQuestions(NotesListActivity.this, name, birthday)) {
Log.i(TAG, "Security questions verified successfully for folder: " + folderId);
// 验证成功,显示设置新密码的对话框
showSetNewFolderPasswordDialog(folderId);
} else {
Log.w(TAG, "Security questions verification failed for folder password reset");
// 验证失败,显示提示
Toast.makeText(NotesListActivity.this, "密保问题回答错误", Toast.LENGTH_SHORT).show();
}
@ -2012,7 +1629,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNegativeButton("取消", null);
// 显示对话框
builder.show();
@ -2020,35 +1637,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
/**
*
*
* @param folderId ID
*/
private void showSetNewFolderPasswordDialog(final long folderId) {
Log.d(TAG, "Showing set new folder password dialog for folder: " + folderId);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("设置新密码");
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setMessage("验证成功,请设置新密码");
// 创建密码输入框
final EditText passwordInput = new EditText(this);
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput.setHint("请输入新密码");
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
layout.addView(passwordInput);
builder.setView(layout);
builder.setView(passwordInput);
// 设置确定按钮
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newPassword = passwordInput.getText().toString().trim();
String newPassword = passwordInput.getText().toString();
if (!newPassword.isEmpty()) {
Log.i(TAG, "Setting new password for folder: " + folderId);
// 保存新密码
SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
@ -2056,10 +1663,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
editor.apply();
// 显示提示
Toast.makeText(NotesListActivity.this, "密码重置成功,请使用新密码进入文件夹", Toast.LENGTH_SHORT).show();
Log.i(TAG, "Folder password reset successfully for folder: " + folderId);
Toast.makeText(NotesListActivity.this, "密码重置成功", Toast.LENGTH_SHORT).show();
} else {
Log.w(TAG, "New folder password is empty");
// 密码为空,显示提示
Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
}
@ -2067,7 +1672,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe
});
// 设置取消按钮
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNegativeButton("取消", null);
// 显示对话框
builder.show();

@ -21,8 +21,6 @@ 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;
@ -37,8 +35,6 @@ 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;
@ -49,24 +45,34 @@ 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);
// 设置ActionBar
android.app.ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
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);
}
}
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
@ -76,13 +82,6 @@ public class NotesPreferenceActivity extends PreferenceActivity {
@Override
protected void onResume() {
super.onResume();
// 检查并设置密保问题(只在首次设置时显示,且只检查一次)
if (!mHasCheckedSecurityQuestions && !hasSecurityQuestionsSet()) {
mHasCheckedSecurityQuestions = true;
showSetSecurityQuestionsDialog();
}
loadSecurityPreference();
loadUserCenterPreference();
}
@ -339,23 +338,21 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
});
builder.show();
// 添加修改密保问题的选项
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;
}
});
Preference securityPref = new Preference(this);
securityPref.setTitle("修改密保问题");
securityPref.setSummary("修改用于重置密码的个人信息");
securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
// 验证当前密保才能修改
showVerifySecurityQuestionsForModificationDialog();
return true;
}
});
mAccountCategory.addPreference(securityPref); // 添加密保设置选项
}
mAccountCategory.addPreference(accountPref); // 添加到设置类别
mAccountCategory.addPreference(securityPref); // 添加密保设置选项
}
/**

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/on_primary"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/text_secondary"
android:pathData="M12.5,20C17.2,20 21,16.2 21,11.5S17.2,3 12.5,3 4,6.8 4,11.5 7.8,20 12.5,20zM13,13h5v-2h-5V8h-2v3H6v2h5v3h2v-3z" />
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.6,10.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4L7,4v14h7.04c2.09,0 3.71,-1.7 3.71,-3.79 0,-1.52 -0.86,-2.82 -2.15,-3.42zM10,6.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,15.5L10,15.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9,4v3h5v12h3V7h5V4H9zM3,12h8v-3H3v3z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10,19h4v-3h-4v3zM5,4v3h5v3h4V7h5V4H5zM3,14h18v-2H3v2z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,17c3.31,0 6,-2.69 6,-6V3h-2.5v8c0,1.93 -1.57,3.5 -3.5,3.5S8.5,12.93 8.5,11V3H6v8c0,3.31 2.69,6 6,6zM5,19v2h14v-2H5z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h2v-2H3v2zm0,4h2v-2H3v2zm0,-8h2V7H3v2zm4,4h14v-2H7v2zm0,4h14v-2H7v2zM7,7v2h14V7H7z"/>
</vector>

@ -1,5 +0,0 @@
<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="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

@ -1,5 +0,0 @@
<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="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
</vector>

@ -1,10 +0,0 @@
<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>

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/surface"
android:elevation="4dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_login"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="32dp" />
<EditText
android:id="@+id/et_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_username"
android:inputType="text"
android:maxLines="1"
android:layout_marginBottom="16dp"
android:padding="12dp"
android:background="@drawable/edit_white" />
<EditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_login_password"
android:inputType="textPassword"
android:maxLines="1"
android:layout_marginBottom="24dp"
android:padding="12dp"
android:background="@drawable/edit_white" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_login"
android:layout_marginBottom="16dp"
android:backgroundTint="@color/primary"
android:textColor="@color/on_primary" />
<Button
android:id="@+id/btn_login_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/cancel"
android:backgroundTint="@color/surface"
android:textColor="@color/text_primary" />
<TextView
android:id="@+id/tv_login_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_register_account"
android:textColor="@color/text_link"
android:layout_marginTop="24dp"
android:padding="8dp" />
</LinearLayout>
</LinearLayout>

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/surface"
android:elevation="4dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_register"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="32dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_vertical">
<EditText
android:id="@+id/et_register_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_username"
android:inputType="text"
android:maxLines="1"
android:layout_marginBottom="16dp"
android:padding="12dp"
android:background="@drawable/edit_white" />
<EditText
android:id="@+id/et_register_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_login_password"
android:inputType="textPassword"
android:maxLines="1"
android:layout_marginBottom="16dp"
android:padding="12dp"
android:background="@drawable/edit_white" />
<EditText
android:id="@+id/et_register_confirm_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_confirm_password"
android:inputType="textPassword"
android:maxLines="1"
android:layout_marginBottom="24dp"
android:padding="12dp"
android:background="@drawable/edit_white" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Button
android:id="@+id/btn_register_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
android:layout_marginEnd="8dp"
android:backgroundTint="@color/surface"
android:textColor="@color/text_primary" />
<Button
android:id="@+id/btn_register"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_register"
android:layout_marginStart="8dp"
android:backgroundTint="@color/primary"
android:textColor="@color/on_primary" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/preferences_add_account" />
</LinearLayout>

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/camera_preview_title"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp" />
<ImageView
android:id="@+id/preview_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:layout_marginBottom="16dp"
android:contentDescription="@string/camera_preview_image" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/btn_retake"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/camera_retake"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/camera_confirm"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/surface"
app:strokeColor="@color/outline"
app:strokeWidth="1dp"
app:cardPreventCornerOverlap="true"
app:contentPadding="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="0dp">
<!-- 标题区域 -->
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:text="@string/image_insert_title"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:textColor="@color/text_primary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 图库选项卡片 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/btn_gallery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:padding="16dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@+id/title_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/gallery_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_gallery"
app:tint="@color/primary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/gallery_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/image_insert_gallery"
android:textSize="16sp"
android:textColor="@color/text_primary"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/gallery_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 分隔线 -->
<View
android:id="@+id/divider1"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/divider"
app:layout_constraintTop_toBottomOf="@+id/btn_gallery"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 相机选项卡片 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/btn_camera"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:padding="16dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@+id/divider1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/camera_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_camera"
app:tint="@color/primary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/camera_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/image_insert_camera"
android:textSize="16sp"
android:textColor="@color/text_primary"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/camera_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 分隔线 -->
<View
android:id="@+id/divider2"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/divider"
app:layout_constraintTop_toBottomOf="@+id/btn_camera"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 取消按钮 -->
<TextView
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="16dp"
android:text="@android:string/cancel"
android:textSize="16sp"
android:textColor="@color/primary"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
app:layout_constraintTop_toBottomOf="@+id/divider2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:background="@android:color/white"
android:padding="4dp">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:maxHeight="400dp"
android:scaleType="fitCenter"
android:contentDescription="@string/image_content_description" />
<ImageButton
android:id="@+id/btn_delete"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="top|end"
android:layout_margin="8dp"
android:background="@android:drawable/ic_menu_delete"
android:contentDescription="@string/delete_image" />
<TextView
android:id="@+id/tv_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_margin="8dp"
android:background="@android:color/holo_blue_light"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="gone" />
</FrameLayout>

@ -15,185 +15,81 @@
limitations under the License.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/surface"
android:elevation="4dp">
<!-- 格式化工具栏容器 -->
<LinearLayout
android:id="@+id/format_toolbar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_gravity="start|center_vertical">
<ImageButton
android:id="@+id/btn_font_size"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_format_size"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_font_size"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<View
android:layout_width="1dp"
android:layout_height="20dp"
android:background="@color/divider"
android:layout_marginHorizontal="2dp" />
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_format_bold"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_bold"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_format_italic"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_italic"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_format_underline"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_underline"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<ImageButton
android:id="@+id/btn_strikethrough"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_format_strikethrough"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_strikethrough"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<View
android:layout_width="1dp"
android:layout_height="20dp"
android:background="@color/divider"
android:layout_marginHorizontal="4dp" />
<ImageButton
android:id="@+id/btn_list_mode"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_list_mode"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_list_mode"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
<ImageButton
android:id="@+id/btn_insert_image"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_insert_image"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_insert_image"
android:scaleType="centerInside"
app:tint="@color/text_primary" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
<LinearLayout
android:id="@+id/note_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp">
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="0dp"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@style/TextAppearanceSecondaryItem"
android:gravity="center_vertical" />
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem"
android:layout_marginStart="2dp"
android:layout_marginEnd="8dp" />
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dip"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<!-- 移除无功能的ImageButton -->
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dp">
android:fadingEdgeLength="0dip">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:background="@null"
@ -203,41 +99,29 @@
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
<LinearLayout
android:id="@+id/note_attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dp"
android:layout_marginLeft="-10dip"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dp"
android:layout_width="43dp"
android:src="@android:drawable/ic_menu_edit"
app:tint="@color/text_primary"
android:layout_gravity="top|right"
android:layout_marginTop="?attr/actionBarSize"
android:contentDescription="@string/change_background_color"
android:background="?attr/selectableItemBackgroundBorderless" />
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<LinearLayout
android:id="@+id/note_bg_color_selector"
@ -513,4 +397,4 @@
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>

@ -15,116 +15,64 @@
limitations under the License.
-->
<com.google.android.material.card.MaterialCardView
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/note_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/surface"
app:strokeWidth="0dp"
app:rippleColor="@color/ripple">
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="16dp">
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
<LinearLayout
android:id="@+id/content_container"
android:layout_width="0dp"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/alert_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone"
android:layout_marginBottom="4dp" />
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_lock_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_lock"
android:tint="@color/text_secondary"
android:visibility="gone"
android:layout_marginEnd="4dp" />
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:textColor="@color/text_primary"
android:ellipsize="end"
android:layout_marginEnd="8dp" />
android:singleLine="true" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem"
android:textColor="@color/text_secondary" />
android:textAppearance="@style/TextAppearanceSecondaryItem" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/alert_container"
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:contentDescription="@string/menu_alert"
android:src="@drawable/ic_alarm"
app:tint="@color/text_secondary"
android:visibility="gone" />
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:visibility="gone"
android:layout_marginStart="8dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
</LinearLayout>
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"/>
</FrameLayout>

@ -15,112 +15,44 @@
limitations under the License.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:visibility="gone"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
<ListView
android:id="@+id/notes_list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null" />
</LinearLayout>
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/surface"
android:elevation="4dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearanceMedium"
android:textColor="@color/text_primary" />
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="8dp"
android:visibility="gone">
<EditText
android:id="@+id/search_input"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:hint="搜索便签"
android:inputType="text"
android:imeOptions="actionSearch"
android:singleLine="true"
android:background="@android:drawable/edit_text"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:textColor="@android:color/black"
android:textColorHint="@android:color/darker_gray"
android:textSize="16sp" />
<Button
android:id="@+id/search_cancel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="取消"
android:background="@android:color/transparent"
android:textColor="@android:color/holo_blue_light"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="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>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_new_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="@string/notelist_menu_new"
android:src="@drawable/ic_add"
app:backgroundTint="@color/primary"
app:tint="@color/on_primary"
app:fabSize="normal"
app:elevation="6dp"
app:pressedTranslationZ="12dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
android:focusable="false"
android:layout_gravity="bottom" />
</FrameLayout>

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="8dp"
android:background="@android:color/transparent">
<EditText
android:id="@+id/search_input"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:hint="搜索便签"
android:inputType="text"
android:imeOptions="actionSearch"
android:singleLine="true"
android:background="@android:drawable/edit_text"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:textColor="@android:color/black"
android:textColorHint="@android:color/darker_gray"
android:textSize="16sp" />
<Button
android:id="@+id/search_cancel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="取消"
android:background="@android:color/transparent"
android:textColor="@android:color/holo_blue_light"
android:textSize="16sp" />
</LinearLayout>

@ -3,7 +3,7 @@
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may use this file except in compliance with 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
@ -21,11 +21,21 @@
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/surface"
android:elevation="4dp" />
<Button
android:id="@+id/preference_sync_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dip"
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
style="?android:attr/textAppearanceMedium"
android:text="@string/preferences_button_sync_immediately"/>
<TextView
android:id="@+id/prefenerece_sync_status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

@ -16,23 +16,23 @@
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<!-- 插入图片、列表模式已移至工具栏 -->
<item
android:id="@+id/menu_delete"
android:icon="@android:drawable/ic_menu_delete"
android:title="@string/menu_delete"
app:showAsAction="never"
android:orderInCategory="4" />
android:title="@string/menu_delete"/>
<!-- 字体大小和格式选项已移至工具栏,从菜单中移除 -->
<item
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
<item
android:id="@+id/menu_share"
@ -50,4 +50,22 @@
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
</menu>
<item
android:id="@+id/menu_format"
android:title="@string/menu_format">
<menu>
<item
android:id="@+id/menu_bold"
android:title="@string/menu_bold" />
<item
android:id="@+id/menu_italic"
android:title="@string/menu_italic" />
<item
android:id="@+id/menu_underline"
android:title="@string/menu_underline" />
<item
android:id="@+id/menu_strikethrough"
android:title="@string/menu_strikethrough" />
</menu>
</item>
</menu>

@ -28,22 +28,10 @@
android:title="@string/menu_delete"
android:icon="@drawable/menu_delete"
android:showAsAction="always|withText" />
<item
android:id="@+id/lock"
android:title="@string/menu_lock"
android:icon="@drawable/ic_lock"
android:showAsAction="always|withText" />
<item
android:id="@+id/unlock"
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:title="置顶"
android:icon="@android:drawable/star_big_on"
android:showAsAction="always|withText" />
</menu>

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<!-- Dark Mode Color Overrides -->
<!-- Background Colors -->
<color name="background">#121212</color>
<color name="surface">#1E1E1E</color>
<color name="surface_variant">#2D2D2D</color>
<!-- Text Colors -->
<color name="text_primary">#E0E0E0</color>
<color name="text_secondary">#A0A0A0</color>
<color name="text_hint">#757575</color>
<!-- On Colors -->
<color name="on_background">#E0E0E0</color>
<color name="on_surface">#E0E0E0</color>
<color name="on_surface_variant">#B0B0B0</color>
<!-- Note Background Colors (Dark Mode) -->
<color name="note_bg_yellow">#4A4536</color>
<color name="note_bg_red">#4A3636</color>
<color name="note_bg_blue">#364A5A</color>
<color name="note_bg_green">#364A3E</color>
<color name="note_bg_white">#1E1E1E</color>
<!-- Divider Colors -->
<color name="divider">#2D2D2D</color>
<color name="outline">#404040</color>
<!-- Ripple Effect -->
<color name="ripple">#3352D399</color>
<!-- Status Bar & Navigation Bar -->
<color name="status_bar">#121212</color>
<color name="navigation_bar">#121212</color>
</resources>

@ -1,20 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme for dark mode. -->
<style name="Base.Theme.NotesMaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Override light status bar and navigation bar for dark mode -->
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
<!-- Ensure proper contrast for dark mode -->
<item name="android:textColorPrimary">@color/text_primary</item>
<item name="android:textColorSecondary">@color/text_secondary</item>
<item name="android:textColorHint">@color/text_hint</item>
</style>
<!-- NoteTheme dark mode overrides (inherits from Theme.NotesMaster) -->
<style name="NoteTheme" parent="Theme.NotesMaster">
<!-- Override for dark mode -->
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
<!-- Base application theme. -->
<style name="Base.Theme.Notesmaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

@ -58,11 +58,6 @@
<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,11 +59,6 @@
<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>

@ -17,57 +17,4 @@
<resources>
<color name="user_query_highlight">#335b5b5b</color>
<!-- Material Design 3 Color System -->
<!-- Primary Colors -->
<color name="primary">#34D399</color>
<color name="primary_container">#DCFCE7</color>
<color name="on_primary">#FFFFFF</color>
<color name="on_primary_container">#064E3B</color>
<!-- Secondary Colors -->
<color name="secondary">#6B7280</color>
<color name="secondary_container">#E5E7EB</color>
<color name="on_secondary">#FFFFFF</color>
<color name="on_secondary_container">#1F2937</color>
<!-- Background Colors -->
<color name="background">#FAFAFA</color>
<color name="surface">#FFFFFF</color>
<color name="surface_variant">#F3F4F6</color>
<color name="on_background">#1F2937</color>
<color name="on_surface">#1F2937</color>
<color name="on_surface_variant">#4B5563</color>
<!-- Note Background Colors (Modern Soft Tones) -->
<color name="note_bg_yellow">#FEF3C7</color>
<color name="note_bg_red">#FEE2E2</color>
<color name="note_bg_blue">#DBEAFE</color>
<color name="note_bg_green">#DCFCE7</color>
<color name="note_bg_white">#FFFFFF</color>
<!-- Text Colors -->
<color name="text_primary">#1F2937</color>
<color name="text_secondary">#6B7280</color>
<color name="text_link">#2563EB</color>
<color name="text_hint">#9CA3AF</color>
<!-- Accent Colors -->
<color name="accent">#34D399</color>
<color name="accent_container">#DCFCE7</color>
<!-- Error Colors -->
<color name="error">#EF4444</color>
<color name="error_container">#FEE2E2</color>
<color name="on_error">#FFFFFF</color>
<!-- Divider Colors -->
<color name="divider">#E5E7EB</color>
<color name="outline">#D1D5DB</color>
<!-- Ripple Effect -->
<color name="ripple">#1A34D399</color>
<!-- Legacy Colors (for backward compatibility) -->
<!-- Note: primary_text_dark and secondary_text_dark are defined in res/color/ directory as selectors -->
</resources>

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
阿里云OSS配置文件
================================
重要提示:以下参数需要您从阿里云控制台获取并填写
================================
获取方式:
1. 登录阿里云控制台https://oss.console.aliyun.com/
2. 创建或选择您的OSS Bucket
3. 在"概览"页面查看Endpoint
4. 在"AccessKey管理"中创建或查看AccessKey
安全建议:
- 不要将AccessKey提交到版本控制系统
- 建议使用RAM子账号并限制权限
- 生产环境建议使用STS临时凭证
-->
<!-- OSS服务端点 - 根据您的Bucket所在区域填写 -->
<!-- 示例华东1杭州https://oss-cn-hangzhou.aliyuncs.com -->
<!-- 示例华北2北京https://oss-cn-beijing.aliyuncs.com -->
<!-- 示例华南1深圳https://oss-cn-shenzhen.aliyuncs.com -->
<string name="oss_endpoint">https://oss-cn-wuhan-lr.aliyuncs.com</string>
<!-- 阿里云AccessKey ID -->
<!-- 在阿里云控制台的"AccessKey管理"中创建 -->
<string name="oss_access_key_id">LTAI5tAiNrYEtYykvN9xNn3w</string>
<!-- 阿里云AccessKey Secret -->
<!-- 在阿里云控制台的"AccessKey管理"中创建 -->
<string name="oss_access_key_secret">JnRHqdTMBIoaONvEPNB8RyypZPADaM</string>
<!-- OSS存储桶名称 -->
<!-- 在阿里云OSS控制台创建Bucket时指定的名称 -->
<string name="oss_bucket_name">mini-notes</string>
<!-- OSS文件存储路径前缀 -->
<!-- 所有便签数据将存储在此路径下 -->
<string name="oss_file_prefix">notes/</string>
<!-- OSS连接超时时间毫秒 -->
<integer name="oss_connection_timeout">300000</integer>
<!-- OSS Socket超时时间毫秒 -->
<integer name="oss_socket_timeout">300000</integer>
<!-- OSS最大重试次数 -->
<integer name="oss_max_retry_count">30</integer>
</resources>

@ -17,188 +17,126 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">小米便签</string>
<string name="app_widget2x2">便签 2x2</string>
<string name="app_widget4x4">便签 4x4</string>
<string name="widget_havenot_content">未找到关联便签,点击创建关联便签。</string>
<string name="widget_under_visit_mode">隐私模式,无法查看便签内容</string>
<string name="app_name">Notes</string>
<string name="app_widget2x2">Notes 2x2</string>
<string name="app_widget4x4">Notes 4x4</string>
<string name="widget_havenot_content">No associated note found, click to create associated note.</string>
<string name="widget_under_visit_mode">Privacy modecan not see note content</string>
<string name="notelist_string_info">...</string>
<string name="notelist_menu_new">新建便签</string>
<string name="delete_remind_time_message">删除提醒成功</string>
<string name="set_remind_time_message">设置提醒</string>
<string name="note_alert_expired">已过期</string>
<string name="notelist_menu_new">Add note</string>
<string name="delete_remind_time_message">Delete reminder successfully</string>
<string name="set_remind_time_message">Set reminder</string>
<string name="note_alert_expired">Expired</string>
<string name="format_date_ymd">yyyyMMdd</string>
<string name="format_datetime_mdhm">MMMd kk:mm</string>
<string name="notealert_ok">知道了</string>
<string name="notealert_enter">查看</string>
<string name="note_link_tel">拨打电话</string>
<string name="note_link_email">发送邮件</string>
<string name="note_link_web">浏览网页</string>
<string name="note_link_other">打开地图</string>
<string name="notealert_ok">Got it</string>
<string name="notealert_enter">Take a look</string>
<string name="note_link_tel">Call</string>
<string name="note_link_email">Send email</string>
<string name="note_link_web">Browse web</string>
<string name="note_link_other">Open map</string>
<!-- Text export file information -->
<string name="file_path">/MIUI/notes/</string>
<string name="file_name_txt_format">notes_%s.txt</string>
<!-- notes list string -->
<string name="format_folder_files_count">(%d)</string>
<string name="menu_create_folder">新建文件夹</string>
<string name="menu_export_text">导出文本</string>
<string name="menu_sync">同步</string>
<string name="menu_sync_cancel">取消同步</string>
<string name="menu_setting">设置</string>
<string name="menu_search">搜索</string>
<string name="menu_delete">删除</string>
<string name="menu_move">移动到文件夹</string>
<string name="menu_select_title">已选择 %d 项</string>
<string name="menu_select_none">未选择任何项目,操作无效</string>
<string name="menu_select_all">全选</string>
<string name="menu_deselect_all">取消全选</string>
<string name="menu_font_size">字体大小</string>
<string name="menu_font_small"></string>
<string name="menu_font_normal"></string>
<string name="menu_font_large"></string>
<string name="menu_font_super">超大</string>
<string name="menu_list_mode">进入清单模式</string>
<string name="menu_normal_mode">退出清单模式</string>
<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_insert_image">插入图片</string>
<string name="image_insert_title">插入图片</string>
<string name="image_insert_gallery">从相册选择</string>
<string name="image_insert_camera">拍照</string>
<string name="image_content_description">附件图片</string>
<string name="delete_image">删除图片</string>
<string name="confirm_delete_image">确定要删除这张图片吗?</string>
<string name="permission_denied">权限被拒绝,无法访问图片</string>
<string name="image_added">图片添加成功</string>
<string name="image_deleted">图片已删除</string>
<string name="failed_to_add_image">添加图片失败</string>
<string name="camera_preview_title">预览照片</string>
<string name="camera_preview_image">预览图片</string>
<string name="camera_retake">重拍</string>
<string name="camera_confirm">确认</string>
<string name="menu_send_to_desktop">发送到桌面</string>
<string name="menu_alert">提醒我</string>
<string name="menu_remove_remind">删除提醒</string>
<string name="menu_title_select_folder">选择文件夹</string>
<string name="menu_move_parent_folder">上级文件夹</string>
<string name="info_note_enter_desktop">便签已添加到桌面</string>
<string name="alert_message_delete_folder">确定要删除文件夹及其中的便签吗?</string>
<string name="alert_title_delete">删除选中的便签</string>
<string name="alert_message_delete_notes">确定要删除选中的 %d 条便签吗?</string>
<string name="alert_message_delete_note">确定要删除这条便签吗?</string>
<string name="format_move_notes_to_folder">已将选中的 %1$d 条便签移动到 %2$s 文件夹</string>
<string name="menu_create_folder">New Folder</string>
<string name="menu_export_text">Export text</string>
<string name="menu_sync">Sync</string>
<string name="menu_sync_cancel">Cancel syncing</string>
<string name="menu_setting">Settings</string>
<string name="menu_search">Search</string>
<string name="menu_delete">Delete</string>
<string name="menu_move">Move to folder</string>
<string name="menu_select_title">%d selected</string>
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<string name="menu_select_all">Select all</string>
<string name="menu_deselect_all">Deselect all</string>
<string name="menu_font_size">Font size</string>
<string name="menu_font_small">Small</string>
<string name="menu_font_normal">Medium</string>
<string name="menu_font_large">Large</string>
<string name="menu_font_super">Super</string>
<string name="menu_list_mode">Enter check list</string>
<string name="menu_normal_mode">Leave check list</string>
<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="folder_exist">The folder %1$s exist, please rename</string>
<string name="menu_share">Share</string>
<string name="menu_send_to_desktop">Send to home</string>
<string name="menu_alert">Remind me</string>
<string name="menu_remove_remind">Delete reminder</string>
<string name="menu_format">Format</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_title_select_folder">Select folder</string>
<string name="menu_move_parent_folder">Parent folder</string>
<string name="info_note_enter_desktop">Note added to home</string>
<string name="alert_message_delete_folder">Confirm to delete folder and its notes?</string>
<string name="alert_title_delete">Delete selected notes</string>
<string name="alert_message_delete_notes">Confirm to delete the selected %d notes?</string>
<string name="alert_message_delete_note">Confirm to delete this note?</string>
<string name="format_move_notes_to_folder">Have moved selected %1$d notes to %2$s folder</string>
<!-- Error information -->
<string name="error_sdcard_unmounted">SD卡正忙暂不可用</string>
<string name="error_sdcard_export">导出失败请检查SD卡</string>
<string name="error_note_not_exist">便签不存在</string>
<string name="error_note_empty_for_clock">抱歉,无法在空白便签上设置闹钟</string>
<string name="error_note_empty_for_send_to_desktop">抱歉,无法将空白便签发送到桌面</string>
<string name="success_sdcard_export">导出成功</string>
<string name="failed_sdcard_export">导出失败</string>
<string name="format_exported_file_location">导出文本文件 (%1$s) 到SD卡 (%2$s) 目录</string>
<string name="error_sdcard_unmounted">SD card busy, not available now</string>
<string name="error_sdcard_export">Export failed, please check SD card</string>
<string name="error_note_not_exist">The note is not exist</string>
<string name="error_note_empty_for_clock">Sorry, can not set clock on empty note</string>
<string name="error_note_empty_for_send_to_desktop">Sorry, can not send and empty note to home</string>
<string name="success_sdcard_export">Export successful</string>
<string name="failed_sdcard_export">Export fail</string>
<string name="format_exported_file_location">Export text file (%1$s) to SD (%2$s) directory</string>
<!-- Sync -->
<string name="ticker_syncing">Syncing notes...</string>
<string name="ticker_success">Sync is successful</string>
<string name="ticker_fail">Sync is failed</string>
<string name="ticker_cancel">Sync is canceled</string>
<string name="success_sync_account">Sync is successful with account %1$s</string>
<string name="error_sync_network">Sync failed, please check network and account settings</string>
<string name="error_sync_internal">Sync failed, internal error occurs</string>
<string name="error_sync_cancelled">Sync is canceled</string>
<string name="sync_progress_login">Logging into %1$s...</string>
<string name="sync_progress_init_list">Getting remote note list...</string>
<string name="sync_progress_syncing">Synchronize local notes with Google Task...</string>
<!-- Preferences -->
<string name="preferences_title">Settings</string>
<string name="preferences_account_title">Sync account</string>
<string name="preferences_account_summary">Sync notes with google task</string>
<string name="preferences_last_sync_time">Last sync time %1$s</string>
<string name="preferences_last_sync_time_format">yyyy-MM-dd hh:mm:ss</string>
<string name="preferences_add_account">Add account</string>
<string name="preferences_menu_change_account">Change sync account</string>
<string name="preferences_menu_remove_account">Remove sync account</string>
<string name="preferences_menu_cancel">Cancel</string>
<string name="preferences_button_sync_immediately">Sync immediately</string>
<string name="preferences_button_sync_cancel">Cancel syncing</string>
<string name="preferences_dialog_change_account_title">Current account %1$s</string>
<string name="preferences_dialog_change_account_warn_msg">All sync related information will be deleted, which may result in duplicated items sometime</string>
<string name="preferences_dialog_select_account_title">Sync notes</string>
<string name="preferences_dialog_select_account_tips">Please select a google account. Local notes will be synced with google task.</string>
<string name="preferences_toast_cannot_change_account">Cannot change the account because sync is in progress</string>
<string name="preferences_toast_success_set_accout">%1$s has been set as the sync account</string>
<string name="preferences_bg_random_appear_title">New note background color random</string>
<string name="preferences_elder_mode_title">Elderly Mode</string>
<string name="preferences_elder_mode_summary">Use larger font size for better readability</string>
<string name="preferences_title">设置</string>
<string name="preferences_user_center_title">用户中心</string>
<string name="preferences_user_center_summary">当前用户:%1$s</string>
<string name="preferences_user_center_not_logged_in">未登录</string>
<string name="menu_login">登录</string>
<string name="menu_register">注册</string>
<string name="menu_logout">退出登录</string>
<string name="toast_logout_success">退出登录成功</string>
<string name="preferences_bg_random_appear_title">新建便签背景颜色随机</string>
<string name="preferences_elder_mode_title">长辈模式</string>
<string name="button_delete">Delete</string>
<string name="call_record_folder_name">Call notes</string>
<string name="hint_foler_name">Input name</string>
<string name="button_delete">删除</string>
<string name="call_record_folder_name">通话便签</string>
<string name="hint_foler_name">输入名称</string>
<!-- 用户登录/注册相关字符串 -->
<string name="title_login">登录</string>
<string name="title_register">注册</string>
<string name="btn_login">登录</string>
<string name="btn_register">注册</string>
<string name="cancel">取消</string>
<string name="text_register_account">还没有账号?立即注册</string>
<string name="hint_username">用户名</string>
<string name="hint_login_password">密码</string>
<string name="hint_confirm_password">确认密码</string>
<string name="error_username_empty">用户名不能为空</string>
<string name="error_login_failed">用户名或密码错误</string>
<string name="error_username_exists">用户名已存在</string>
<string name="error_register_failed">注册失败</string>
<string name="toast_login_success">登录成功</string>
<string name="toast_register_success">注册成功</string>
<string name="error_sync_not_available">同步功能已不再可用</string>
<string name="search_label">搜索便签</string>
<string name="search_hint">搜索便签</string>
<string name="search_setting_description">便签中的文本</string>
<string name="search">便签</string>
<string name="datetime_dialog_ok">设置</string>
<string name="datetime_dialog_cancel">取消</string>
<string name="search_label">Searching Notes</string>
<string name="search_hint">Search notes</string>
<string name="search_setting_description">Text in your notes</string>
<string name="search">Notes</string>
<string name="datetime_dialog_ok">set</string>
<string name="datetime_dialog_cancel">cancel</string>
<plurals name="search_results_title">
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> 条结果 \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> 条结果 \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
</plurals>
<string name="change_background_color">更改背景颜色</string>
<!-- 便签加锁相关字符串 -->
<string name="menu_lock">加锁</string>
<string name="menu_unlock">解锁</string>
<string name="menu_security_settings">加密设置</string>
<string name="menu_set_password">设置密码</string>
<string name="menu_change_password">修改密码</string>
<string name="menu_clear_password">清除密码</string>
<string name="preferences_security_category">安全设置</string>
<string name="preferences_security_title">加密设置</string>
<string name="preferences_security_summary">设置、修改或清除便签密码</string>
<string name="preferences_password_set">密码已设置</string>
<string name="preferences_password_not_set">密码未设置</string>
<string name="title_locked_note">便签已加锁</string>
<string name="title_unlock_note">解锁便签</string>
<string name="title_set_password">设置密码</string>
<string name="title_change_password">修改密码</string>
<string name="title_verify_password">验证密码</string>
<string name="hint_enter_password">请输入密码</string>
<string name="btn_forgot_password">忘记密码</string>
<string name="title_forgot_password">忘记密码</string>
<string name="hint_password">密码</string>
<string name="hint_new_password">新密码</string>
<string name="hint_old_password">原密码</string>
<string name="error_wrong_password">密码错误</string>
<string name="error_no_password_set">请先设置密码</string>
<string name="error_password_empty">密码不能为空</string>
<string name="error_password_mismatch">两次密码不一致</string>
<string name="error_operation_failed">操作失败</string>
<string name="toast_lock_success">加锁成功</string>
<string name="toast_unlock_success">解锁成功</string>
<string name="toast_password_set_success">密码设置成功</string>
<string name="toast_password_change_success">密码修改成功</string>
<string name="toast_password_cleared">密码已清除</string>
<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>
<!-- OSS云同步相关字符串 -->
<string name="oss_config_invalid">OSS配置无效请检查配置文件</string>
<string name="oss_upload_failed">上传到云端失败</string>
</resources>

@ -15,119 +15,54 @@
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<style name="TextAppearanceSuper">
<item name="android:textSize">@dimen/text_font_size_super</item>
<item name="android:textColorLink">@color/text_link</item>
<item name="android:lineSpacingMultiplier">1.4</item>
<item name="android:textColorLink">#0000ff</item>
</style>
<style name="TextAppearanceLarge">
<item name="android:textSize">@dimen/text_font_size_large</item>
<item name="android:textColorLink">@color/text_link</item>
<item name="android:lineSpacingMultiplier">1.3</item>
<item name="android:textColorLink">#0000ff</item>
</style>
<style name="TextAppearanceMedium">
<item name="android:textSize">@dimen/text_font_size_medium</item>
<item name="android:textColorLink">@color/text_link</item>
<item name="android:lineSpacingMultiplier">1.3</item>
<item name="android:textColorLink">#0000ff</item>
</style>
<style name="TextAppearanceNormal">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColorLink">@color/text_link</item>
<item name="android:lineSpacingMultiplier">1.4</item>
<item name="android:textColorLink">#0000ff</item>
</style>
<style name="TextAppearancePrimaryItem">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:lineSpacingMultiplier">1.3</item>
<item name="android:textColor">@color/primary_text_dark</item>
</style>
<style name="TextAppearanceSecondaryItem">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:lineSpacingMultiplier">1.3</item>
<item name="android:textColor">@color/secondary_text_dark</item>
</style>
<style name="TextAppearanceUnderMenuIcon">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:textColor">@android:color/black</item>
</style>
<style name="HighlightTextAppearancePrimary">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:textColor">@color/primary_text_dark</item>
</style>
<style name="HighlightTextAppearanceSecondary">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:textColor">@color/secondary_text_dark</item>
</style>
<!-- Material Design 3 Styles -->
<style name="NoteTheme" parent="Theme.NotesMaster">
<!-- Inherits all configuration from Theme.NotesMaster -->
<!-- Additional dialog-specific styling if needed -->
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
</style>
<style name="NoteActionBarStyle" parent="Theme.Material3.DayNight">
<item name="android:background">@color/surface</item>
<item name="android:elevation">4dp</item>
</style>
<!-- Material Card Style -->
<style name="MaterialCard" parent="Widget.Material3.CardView.Elevated">
<item name="cardCornerRadius">12dp</item>
<item name="cardElevation">2dp</item>
<item name="cardBackgroundColor">@color/surface</item>
<item name="contentPadding">16dp</item>
</style>
<!-- Material Button Styles -->
<style name="MaterialButton" parent="Widget.Material3.Button">
<item name="android:textAllCaps">false</item>
<item name="cornerRadius">8dp</item>
<item name="android:paddingStart">24dp</item>
<item name="android:paddingEnd">24dp</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
</style>
<style name="MaterialButtonOutlined" parent="Widget.Material3.Button.OutlinedButton">
<item name="android:textAllCaps">false</item>
<item name="cornerRadius">8dp</item>
<item name="strokeColor">@color/outline</item>
<item name="strokeWidth">1dp</item>
<item name="android:paddingStart">24dp</item>
<item name="android:paddingEnd">24dp</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
</style>
<!-- Material FAB Style -->
<style name="MaterialFAB" parent="Widget.Material3.FloatingActionButton.Primary">
<item name="fabSize">normal</item>
<item name="backgroundTint">@color/primary</item>
<item name="tint">@color/on_primary</item>
</style>
<!-- Material EditText Style -->
<style name="MaterialEditText" parent="Widget.Material3.TextInputLayout.OutlinedBox">
<item name="boxStrokeColor">@color/outline</item>
<item name="boxCornerRadiusBottomEnd">8dp</item>
<item name="boxCornerRadiusBottomStart">8dp</item>
<item name="boxCornerRadiusTopEnd">8dp</item>
<item name="boxCornerRadiusTopStart">8dp</item>
</style>
<!-- Legacy Theme (for backward compatibility) -->
<style name="NoteThemeLegacy" parent="@android:style/Theme.Holo.Light">
<style name="NoteTheme" parent="@android:style/Theme.Holo.Light">
<item name="android:actionBarStyle">@style/NoteActionBarStyle</item>
</style>
<style name="NoteActionBarStyleLegacy" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<item name="android:visibility">visible</item>
</style>
</resources>

@ -1,45 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme with Material Design 3 -->
<style name="Base.Theme.NotesMaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Primary Colors -->
<item name="colorPrimary">@color/primary</item>
<item name="colorOnPrimary">@color/on_primary</item>
<item name="colorPrimaryContainer">@color/primary_container</item>
<item name="colorOnPrimaryContainer">@color/on_primary_container</item>
<!-- Secondary Colors -->
<item name="colorSecondary">@color/secondary</item>
<item name="colorOnSecondary">@color/on_secondary</item>
<item name="colorSecondaryContainer">@color/secondary_container</item>
<item name="colorOnSecondaryContainer">@color/on_secondary_container</item>
<!-- Background & Surface Colors -->
<item name="android:windowBackground">@color/background</item>
<item name="android:colorBackground">@color/background</item>
<item name="colorSurface">@color/surface</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="colorOnSurfaceVariant">@color/on_surface_variant</item>
<!-- Error Colors -->
<item name="colorError">@color/error</item>
<item name="colorOnError">@color/on_error</item>
<item name="colorErrorContainer">@color/error_container</item>
<item name="colorOnErrorContainer">@color/on_error</item>
<!-- Text Colors -->
<item name="android:textColorPrimary">@color/text_primary</item>
<item name="android:textColorSecondary">@color/text_secondary</item>
<item name="android:textColorHint">@color/text_hint</item>
<!-- Ripple Effect -->
<item name="android:colorControlHighlight">@color/ripple</item>
<!-- Status Bar & Navigation Bar -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
<!-- Base application theme. -->
<style name="Base.Theme.Notesmaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.NotesMaster" parent="Base.Theme.NotesMaster" />
<style name="Theme.Notesmaster" parent="Base.Theme.Notesmaster" />
</resources>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="external_files" path="." />
<files-path name="files" path="." />
<cache-path name="cache" path="." />
<external-path name="external" path="." />
</paths>

@ -17,10 +17,8 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_user_center_title">
<Preference
android:key="pref_user_center"
android:title="@string/preferences_user_center_title" />
<PreferenceCategory
android:key="pref_sync_account_key">
</PreferenceCategory>
<PreferenceCategory>
@ -28,12 +26,10 @@
android:key="pref_key_bg_random_appear"
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
<!-- 长辈模式已移除 -->
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences_security_category">
<Preference
android:key="pref_security_settings"
android:title="@string/preferences_security_title" />
<CheckBoxPreference
android:key="pref_key_elder_mode"
android:title="@string/preferences_elder_mode_title"
android:summary="@string/preferences_elder_mode_summary"
android:defaultValue="false" />
</PreferenceCategory>
</PreferenceScreen>

@ -21,7 +21,7 @@
android:hint="@string/search_hint"
android:searchMode="queryRewriteFromText"
android:searchSuggestAuthority="notes"
android:searchSuggestAuthority="micode_notes"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSettingsDescription="@string/search_setting_description"
android:includeInGlobalSearch="true" />

Loading…
Cancel
Save