diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml
index d858d81..f20cdc7 100644
--- a/src/Notesmaster/app/src/main/AndroidManifest.xml
+++ b/src/Notesmaster/app/src/main/AndroidManifest.xml
@@ -62,6 +62,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notesmaster"
+ android:requestLegacyExternalStorage="true"
tools:targetApi="31">
@@ -155,6 +156,16 @@
android:authorities="micode_notes"
android:multiprocess="true" />
+
+
+
+
*/
-public class MainActivity extends AppCompatActivity implements SidebarFragment.OnSidebarItemSelectedListener {
+public class MainActivity extends BaseActivity implements SidebarFragment.OnSidebarItemSelectedListener {
private static final String TAG = "MainActivity";
private ActivityMainBinding binding;
@@ -129,7 +132,23 @@ public class MainActivity extends AppCompatActivity implements SidebarFragment.O
@Override
public void onExportSelected() {
Log.d(TAG, "Export selected");
- // TODO: 实现导出功能
+ BackupUtils backupUtils = BackupUtils.getInstance(this);
+ int state = backupUtils.exportToText();
+ switch (state) {
+ case BackupUtils.STATE_SUCCESS:
+ Toast.makeText(this, getString(R.string.format_exported_file_location,
+ backupUtils.getExportedTextFileName(),
+ backupUtils.getExportedTextFileDir()), Toast.LENGTH_SHORT).show();
+ break;
+ case BackupUtils.STATE_SD_CARD_UNMOUONTED:
+ Toast.makeText(this, R.string.error_sdcard_unmounted, Toast.LENGTH_SHORT).show();
+ break;
+ case BackupUtils.STATE_SYSTEM_ERROR:
+ default:
+ Toast.makeText(this, R.string.error_sdcard_export, Toast.LENGTH_SHORT).show();
+ break;
+ }
+ closeSidebar();
}
@Override
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java b/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java
index 74e1fea..ec92aaa 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/NotesApplication.java
@@ -15,10 +15,18 @@ import android.provider.Settings;
import com.google.android.material.color.DynamicColors;
+import net.micode.notes.tool.LocaleHelper;
+import android.content.Context;
+
public class NotesApplication extends Application {
private static final String TAG = "NotesApplication";
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(LocaleHelper.onAttach(base));
+ }
+
@Override
public void onCreate() {
super.onCreate();
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
index 169d437..3946522 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
@@ -32,6 +32,7 @@ import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.Note;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.sync.SyncConstants;
+import net.micode.notes.tool.ResourceParser;
import java.util.ArrayList;
import java.util.HashMap;
@@ -428,6 +429,23 @@ public class NotesRepository {
return notes;
}
+ /**
+ * 查询单个笔记信息(异步版本)
+ *
+ * @param noteId 笔记ID
+ * @param callback 回调接口
+ */
+ public void getNoteInfo(long noteId, Callback callback) {
+ executor.execute(() -> {
+ try {
+ NoteInfo noteInfo = getFolderInfo(noteId);
+ callback.onSuccess(noteInfo);
+ } catch (Exception e) {
+ callback.onError(e);
+ }
+ });
+ }
+
/**
* 查询单个文件夹信息
*
@@ -633,6 +651,11 @@ public class NotesRepository {
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.SNIPPET, "");
+ // 设置默认背景颜色(支持随机背景设置)
+ if (context != null) {
+ values.put(NoteColumns.BG_COLOR_ID, ResourceParser.getDefaultBgId(context));
+ }
+
Uri uri = contentResolver.insert(Notes.CONTENT_NOTE_URI, values);
Long noteId = 0L;
@@ -1536,6 +1559,23 @@ public class NotesRepository {
});
}
+ /**
+ * 获取笔记完整内容(异步版本)
+ *
+ * @param noteId 笔记ID
+ * @param callback 回调接口
+ */
+ public void getNoteContent(long noteId, Callback callback) {
+ executor.execute(() -> {
+ try {
+ String content = getNoteContent(noteId);
+ callback.onSuccess(content);
+ } catch (Exception e) {
+ callback.onError(e);
+ }
+ });
+ }
+
private String getNoteContent(long noteId) {
String content = "";
Cursor cursor = contentResolver.query(
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java
index 10c3994..cf84b56 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java
@@ -116,6 +116,27 @@ public class BackupUtils {
return mTextExport.exportToText();
}
+ /**
+ * 导出单条笔记为文本文件
+ *
+ * @param noteId 笔记 ID
+ * @param title 笔记标题(用于文件名)
+ * @return 导出状态码
+ */
+ public int exportNoteToText(String noteId, String title) {
+ return mTextExport.exportNoteToText(noteId, title);
+ }
+
+ /**
+ * 批量导出指定笔记为文本文件
+ *
+ * @param noteIds 笔记 ID 列表
+ * @return 导出状态码
+ */
+ public int exportNotesToText(java.util.List noteIds) {
+ return mTextExport.exportNotesToText(noteIds);
+ }
+
/**
* 获取导出的文本文件名
*
@@ -310,6 +331,72 @@ public class BackupUtils {
}
}
+ /**
+ * 导出单条笔记为文本文件
+ *
+ * @param noteId 笔记 ID
+ * @param title 笔记标题
+ * @return 导出状态码
+ */
+ public int exportNoteToText(String noteId, String title) {
+ if (!externalStorageAvailable()) {
+ Log.d(TAG, "Media was not mounted");
+ return STATE_SD_CARD_UNMOUONTED;
+ }
+
+ PrintStream ps = getExportNotePrintStream(title);
+ if (ps == null) {
+ Log.e(TAG, "get print stream error");
+ return STATE_SYSTEM_ERROR;
+ }
+
+ exportNoteToText(noteId, ps);
+ ps.close();
+
+ return STATE_SUCCESS;
+ }
+
+ /**
+ * 批量导出指定笔记为文本文件
+ *
+ * @param noteIds 笔记 ID 列表
+ * @return 导出状态码
+ */
+ public int exportNotesToText(java.util.List noteIds) {
+ if (!externalStorageAvailable()) {
+ Log.d(TAG, "Media was not mounted");
+ return STATE_SD_CARD_UNMOUONTED;
+ }
+
+ PrintStream ps = getExportToTextPrintStream();
+ if (ps == null) {
+ Log.e(TAG, "get print stream error");
+ return STATE_SYSTEM_ERROR;
+ }
+
+ for (Long noteId : noteIds) {
+ Cursor noteCursor = mContext.getContentResolver().query(
+ net.micode.notes.data.Notes.CONTENT_NOTE_URI,
+ NOTE_PROJECTION,
+ net.micode.notes.data.Notes.NoteColumns.ID + "=?",
+ new String[]{String.valueOf(noteId)},
+ null);
+
+ if (noteCursor != null) {
+ if (noteCursor.moveToFirst()) {
+ ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
+ mContext.getString(R.string.format_datetime_mdhm),
+ noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
+ exportNoteToText(String.valueOf(noteId), ps);
+ }
+ noteCursor.close();
+ }
+ }
+ ps.close();
+
+ return STATE_SUCCESS;
+ }
+
/**
* 导出笔记数据为文本文件
*
@@ -383,6 +470,31 @@ public class BackupUtils {
return STATE_SUCCESS;
}
+ /**
+ * 获取导出单条笔记文本文件的输出流
+ *
+ * @param title 笔记标题
+ * @return PrintStream 对象,如果创建失败则返回 null
+ */
+ private PrintStream getExportNotePrintStream(String title) {
+ File file = generateFileWithTitle(mContext, R.string.file_path, title);
+ if (file == null) {
+ Log.e(TAG, "create file to exported failed");
+ return null;
+ }
+ mFileName = file.getName();
+ mFileDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
+ PrintStream ps = null;
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ ps = new PrintStream(fos);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return ps;
+ }
+
/**
* 获取导出文本文件的输出流
*
@@ -399,7 +511,7 @@ public class BackupUtils {
return null;
}
mFileName = file.getName();
- mFileDirectory = mContext.getString(R.string.file_path);
+ mFileDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
@@ -416,31 +528,62 @@ public class BackupUtils {
}
/**
- * 在 SD 卡上生成导出文本文件
+ * 在公用下载目录上生成指定标题的文本文件
+ *
+ * @param context 应用上下文
+ * @param filePathResId 文件路径资源 ID (不再使用,改为公用下载目录)
+ * @param title 笔记标题
+ * @return 生成的文件对象,如果创建失败则返回 null
+ */
+ private static File generateFileWithTitle(Context context, int filePathResId, String title) {
+ // 清理文件名中的非法字符
+ String fileName = title.replaceAll("[\\\\/:*?\"<>|]", "_");
+ if (TextUtils.isEmpty(fileName)) {
+ fileName = "untitled";
+ }
+ fileName = fileName + "_" + System.currentTimeMillis() + ".txt";
+
+ File filedir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ File file = new File(filedir, fileName);
+
+ try {
+ if (!filedir.exists()) {
+ filedir.mkdirs();
+ }
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ return file;
+ } catch (SecurityException | IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * 在公用下载目录上生成导出文本文件
*
* 在指定的路径下创建导出文件,如果目录不存在则创建目录。
*
*
* @param context 应用上下文
- * @param filePathResId 文件路径资源 ID
+ * @param filePathResId 文件路径资源 ID (不再使用,改为公用下载目录)
* @param fileNameFormatResId 文件名格式资源 ID
* @return 生成的文件对象,如果创建失败则返回 null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
- StringBuilder sb = new StringBuilder();
- sb.append(Environment.getExternalStorageDirectory());
- sb.append(context.getString(filePathResId));
- File filedir = new File(sb.toString());
- sb.append(context.getString(
+ File filedir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ String fileName = context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
- System.currentTimeMillis())));
- File file = new File(sb.toString());
+ System.currentTimeMillis()));
+ File file = new File(filedir, fileName);
try {
if (!filedir.exists()) {
// 创建目录
- filedir.mkdir();
+ filedir.mkdirs();
}
if (!file.exists()) {
// 创建文件
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ImageExportHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ImageExportHelper.java
new file mode 100644
index 0000000..be043df
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ImageExportHelper.java
@@ -0,0 +1,117 @@
+package net.micode.notes.tool;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.view.View;
+import android.widget.ScrollView;
+
+import androidx.core.widget.NestedScrollView;
+import androidx.core.content.FileProvider;
+
+import android.os.Environment;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * 图片导出工具类
+ *
+ * 提供将 View 转换为图片并分享的功能。
+ *
+ */
+public class ImageExportHelper {
+ private static final String TAG = "ImageExportHelper";
+
+ /**
+ * 将 View 转换为 Bitmap
+ *
+ * @param view 要转换的 View
+ * @return 转换后的 Bitmap
+ */
+ public static Bitmap viewToBitmap(View view) {
+ if (view instanceof ScrollView || view instanceof NestedScrollView) {
+ int height = 0;
+ if (view instanceof ScrollView) {
+ ScrollView scrollView = (ScrollView) view;
+ for (int i = 0; i < scrollView.getChildCount(); i++) {
+ height += scrollView.getChildAt(i).getHeight();
+ }
+ } else {
+ NestedScrollView nestedScrollView = (NestedScrollView) view;
+ for (int i = 0; i < nestedScrollView.getChildCount(); i++) {
+ height += nestedScrollView.getChildAt(i).getHeight();
+ }
+ }
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(Color.WHITE);
+ view.draw(canvas);
+ return bitmap;
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(Color.WHITE);
+ view.draw(canvas);
+ return bitmap;
+ }
+ }
+
+ /**
+ * 保存 Bitmap 到公用下载目录
+ *
+ * @param context 应用上下文
+ * @param bitmap 要保存的 Bitmap
+ * @param title 笔记标题
+ * @return 生成的文件 Uri,如果保存失败则返回 null
+ */
+ public static Uri saveBitmapToExternal(Context context, Bitmap bitmap, String title) {
+ String fileName = title.replaceAll("[\\\\/:*?\"<>|]", "_");
+ if (fileName.isEmpty()) {
+ fileName = "untitled";
+ }
+ fileName += "_" + System.currentTimeMillis() + ".png";
+
+ File filedir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ File file = new File(filedir, fileName);
+
+ try {
+ if (!filedir.exists()) {
+ filedir.mkdirs();
+ }
+ FileOutputStream stream = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ stream.close();
+ return FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 保存 Bitmap 到缓存目录并返回 Uri
+ *
+ * @param context 应用上下文
+ * @param bitmap 要保存的 Bitmap
+ * @return 文件的 Uri,如果保存失败则返回 null
+ */
+ public static Uri saveBitmapToCache(Context context, Bitmap bitmap) {
+ File cachePath = new File(context.getCacheDir(), "images");
+ cachePath.mkdirs();
+ try {
+ File file = new File(cachePath, "note_export_" + System.currentTimeMillis() + ".png");
+ FileOutputStream stream = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ stream.close();
+ return FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/LocaleHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/LocaleHelper.java
new file mode 100644
index 0000000..9f52c15
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/LocaleHelper.java
@@ -0,0 +1,92 @@
+package net.micode.notes.tool;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import androidx.preference.PreferenceManager;
+
+import java.util.Locale;
+
+public class LocaleHelper {
+
+ private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";
+
+ public static Context onAttach(Context context) {
+ String lang = getPersistedData(context, Locale.getDefault().getLanguage());
+ return setLocale(context, lang);
+ }
+
+ public static Context onAttach(Context context, String defaultLanguage) {
+ String lang = getPersistedData(context, defaultLanguage);
+ return setLocale(context, lang);
+ }
+
+ public static String getLanguage(Context context) {
+ return getPersistedData(context, Locale.getDefault().getLanguage());
+ }
+
+ public static Context setLocale(Context context, String language) {
+ persist(context, language);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return updateResources(context, language);
+ }
+
+ return updateResourcesLegacy(context, language);
+ }
+
+ private static String getPersistedData(Context context, String defaultLanguage) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ // 默认语言改为中文 (zh-CN)
+ return preferences.getString(SELECTED_LANGUAGE, "zh-CN");
+ }
+
+ private static void persist(Context context, String language) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(SELECTED_LANGUAGE, language);
+ editor.apply();
+ }
+
+ private static Context updateResources(Context context, String language) {
+ Locale locale = getLocale(language);
+ Locale.setDefault(locale);
+
+ Configuration configuration = context.getResources().getConfiguration();
+ configuration.setLocale(locale);
+ configuration.setLayoutDirection(locale);
+
+ return context.createConfigurationContext(configuration);
+ }
+
+ private static Context updateResourcesLegacy(Context context, String language) {
+ Locale locale = getLocale(language);
+ Locale.setDefault(locale);
+
+ Resources resources = context.getResources();
+ Configuration configuration = resources.getConfiguration();
+ configuration.locale = locale;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ configuration.setLayoutDirection(locale);
+ }
+
+ resources.updateConfiguration(configuration, resources.getDisplayMetrics());
+
+ return context;
+ }
+
+ private static Locale getLocale(String language) {
+ if (language.equals("zh-CN")) {
+ return Locale.SIMPLIFIED_CHINESE;
+ } else if (language.equals("zh-TW")) {
+ return Locale.TRADITIONAL_CHINESE;
+ } else if (language.equals("en")) {
+ return Locale.ENGLISH;
+ } else if (language.equals("system")) {
+ return Locale.getDefault();
+ }
+ return new Locale(language);
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/PdfExportHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/PdfExportHelper.java
new file mode 100644
index 0000000..d27806c
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/PdfExportHelper.java
@@ -0,0 +1,98 @@
+package net.micode.notes.tool;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.pdf.PdfDocument;
+import android.os.Environment;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * PDF 导出工具类
+ *
+ * 使用 Android PdfDocument API 将笔记导出为 PDF 文件。
+ *
+ */
+public class PdfExportHelper {
+ private static final String TAG = "PdfExportHelper";
+
+ /**
+ * 将笔记导出为 PDF
+ *
+ * @param context 应用上下文
+ * @param title 笔记标题
+ * @param content 笔记内容
+ * @return 生成的 PDF 文件,如果失败则返回 null
+ */
+ public static File exportToPdf(Context context, String title, String content) {
+ PdfDocument document = new PdfDocument();
+ // A4 纸张大小 (595 x 842 points)
+ PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create();
+ PdfDocument.Page page = document.startPage(pageInfo);
+
+ Canvas canvas = page.getCanvas();
+ TextPaint titlePaint = new TextPaint();
+ titlePaint.setColor(Color.BLACK);
+ titlePaint.setTextSize(24);
+ titlePaint.setFakeBoldText(true);
+
+ TextPaint contentPaint = new TextPaint();
+ contentPaint.setColor(Color.BLACK);
+ contentPaint.setTextSize(14);
+
+ int x = 50;
+ int y = 50;
+
+ // 绘制标题
+ canvas.drawText(title, x, y, titlePaint);
+ y += 40;
+
+ // 绘制正文(自动换行)
+ StaticLayout staticLayout;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+ staticLayout = StaticLayout.Builder.obtain(content, 0, content.length(), contentPaint, pageInfo.getPageWidth() - 100)
+ .setAlignment(Layout.Alignment.ALIGN_NORMAL)
+ .setLineSpacing(0.0f, 1.2f)
+ .setIncludePad(false)
+ .build();
+ } else {
+ staticLayout = new StaticLayout(content, contentPaint, pageInfo.getPageWidth() - 100,
+ Layout.Alignment.ALIGN_NORMAL, 1.2f, 0.0f, false);
+ }
+
+ canvas.save();
+ canvas.translate(x, y);
+ staticLayout.draw(canvas);
+ canvas.restore();
+
+ document.finishPage(page);
+
+ // 生成文件名
+ String fileName = title.replaceAll("[\\\\/:*?\"<>|]", "_");
+ if (fileName.isEmpty()) {
+ fileName = "untitled";
+ }
+ fileName += "_" + System.currentTimeMillis() + ".pdf";
+
+ File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
+
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ document.writeTo(fos);
+ fos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ file = null;
+ } finally {
+ document.close();
+ }
+
+ return file;
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java
index 9aec4ef..9211c52 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java
@@ -39,6 +39,8 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException;
+import net.micode.notes.tool.LocaleHelper;
+
/**
* 闹钟提醒活动
*
@@ -55,6 +57,11 @@ import java.io.IOException;
* @see net.micode.notes.tool.DataUtils
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(LocaleHelper.onAttach(newBase));
+ }
// 当前提醒的笔记ID
private long mNoteId;
// 笔记内容摘要
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/BaseActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/BaseActivity.java
new file mode 100644
index 0000000..6eaf636
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/BaseActivity.java
@@ -0,0 +1,12 @@
+package net.micode.notes.ui;
+
+import android.content.Context;
+import androidx.appcompat.app.AppCompatActivity;
+import net.micode.notes.tool.LocaleHelper;
+
+public class BaseActivity extends AppCompatActivity {
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(LocaleHelper.onAttach(newBase));
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java
index df5aa30..9f4605e 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java
@@ -8,7 +8,7 @@ import androidx.appcompat.app.AppCompatActivity;
import net.micode.notes.R;
-public class CapsuleListActivity extends AppCompatActivity {
+public class CapsuleListActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LoginActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LoginActivity.java
index b602a4e..d966396 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LoginActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LoginActivity.java
@@ -43,7 +43,7 @@ import net.micode.notes.viewmodel.LoginViewModel;
* 遵循 MVVM 架构,所有业务逻辑委托给 {@link LoginViewModel}。
*
*/
-public class LoginActivity extends AppCompatActivity {
+public class LoginActivity extends BaseActivity {
private static final String TAG = "LoginActivity";
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index 5a3b1c0..4ef73ba 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -53,6 +53,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import android.graphics.Bitmap;
+import android.net.Uri;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
@@ -61,7 +63,10 @@ import net.micode.notes.model.NoteCommand;
import net.micode.notes.model.UndoRedoManager;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
+import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.ImageExportHelper;
+import net.micode.notes.tool.PdfExportHelper;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
@@ -88,7 +93,13 @@ import net.micode.notes.tool.SmartParser;
import net.micode.notes.data.FontManager;
-public class NoteEditActivity extends AppCompatActivity implements OnClickListener,
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class NoteEditActivity extends BaseActivity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* 笔记头部视图持有者
@@ -453,7 +464,12 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
+ try {
+ String fontSizeStr = mSharedPrefs.getString(PREFERENCE_FONT_SIZE, String.valueOf(ResourceParser.BG_DEFAULT_FONT_SIZE));
+ mFontSizeId = Integer.parseInt(fontSizeStr);
+ } catch (ClassCastException e) {
+ mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
+ }
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
@@ -1030,6 +1046,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
+ case R.id.menu_export:
+ showExportDialog();
+ break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
@@ -1077,6 +1096,135 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
context.startActivity(intent);
}
+ /**
+ * 显示导出选项对话框
+ */
+ private void showExportDialog() {
+ if (mWorkingNote.getNoteId() == 0) {
+ saveNote();
+ }
+ getWorkingText();
+ String content = mWorkingNote.getContent();
+ if (TextUtils.isEmpty(content)) {
+ showToast(R.string.error_note_empty_for_send_to_desktop);
+ return;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("导出便签");
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(50, 20, 50, 20);
+
+ final RadioGroup group = new RadioGroup(this);
+ RadioButton rbText = new RadioButton(this);
+ rbText.setText("导出为文本 (.txt)");
+ rbText.setId(View.generateViewId());
+ group.addView(rbText);
+
+ RadioButton rbImage = new RadioButton(this);
+ rbImage.setText("导出为图片 (.png)");
+ rbImage.setId(View.generateViewId());
+ group.addView(rbImage);
+
+ RadioButton rbPdf = new RadioButton(this);
+ rbPdf.setText("导出为 PDF (.pdf)");
+ rbPdf.setId(View.generateViewId());
+ group.addView(rbPdf);
+
+ group.check(rbText.getId());
+ layout.addView(group);
+
+ final CheckBox cbShare = new CheckBox(this);
+ cbShare.setText("导出后立即分享");
+ layout.addView(cbShare);
+
+ builder.setView(layout);
+
+ builder.setPositiveButton("开始导出", (dialog, which) -> {
+ int checkedId = group.getCheckedRadioButtonId();
+ boolean share = cbShare.isChecked();
+
+ if (checkedId == rbText.getId()) {
+ performExport(0, share);
+ } else if (checkedId == rbImage.getId()) {
+ performExport(1, share);
+ } else if (checkedId == rbPdf.getId()) {
+ performExport(2, share);
+ }
+ });
+
+ builder.setNegativeButton("取消", null);
+ builder.show();
+ }
+
+ private void performExport(int format, boolean share) {
+ String content = mWorkingNote.getContent();
+ String title = getTitleFromContent(content);
+ String noteId = String.valueOf(mWorkingNote.getNoteId());
+
+ switch (format) {
+ case 0: // Text
+ BackupUtils backupUtils = BackupUtils.getInstance(this);
+ int state = backupUtils.exportNoteToText(noteId, title);
+ if (state == BackupUtils.STATE_SUCCESS) {
+ File file = new File(backupUtils.getExportedTextFileDir(), backupUtils.getExportedTextFileName());
+ showToast("已导出至下载目录: " + file.getName());
+ if (share) shareFile(file, "text/plain");
+ } else {
+ showToast("导出文本失败");
+ }
+ break;
+ case 1: // Image
+ binding.svNoteEditScroll.post(() -> {
+ Bitmap bitmap = ImageExportHelper.viewToBitmap(binding.cvEditorSurface);
+ Uri uri = ImageExportHelper.saveBitmapToExternal(NoteEditActivity.this, bitmap, title);
+ if (uri != null) {
+ showToast("已导出图片至下载目录");
+ if (share) shareUri(uri, "image/png");
+ } else {
+ showToast("生成图片失败");
+ }
+ });
+ break;
+ case 2: // PDF
+ File pdfFile = PdfExportHelper.exportToPdf(this, title, content);
+ if (pdfFile != null) {
+ showToast("已导出 PDF 至下载目录: " + pdfFile.getName());
+ if (share) shareFile(pdfFile, "application/pdf");
+ } else {
+ showToast("导出 PDF 失败");
+ }
+ break;
+ }
+ }
+
+ private String getTitleFromContent(String content) {
+ String title = content.trim();
+ int firstNewLine = title.indexOf('\n');
+ if (firstNewLine > 0) {
+ title = title.substring(0, firstNewLine);
+ }
+ if (title.length() > 30) {
+ title = title.substring(0, 30);
+ }
+ return title;
+ }
+
+ private void shareFile(File file, String mimeType) {
+ Uri uri = androidx.core.content.FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
+ shareUri(uri, mimeType);
+ }
+
+ private void shareUri(Uri uri, String mimeType) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(mimeType);
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(Intent.createChooser(intent, "分享便签"));
+ }
+
/**
* 创建新笔记
*
@@ -1742,6 +1890,17 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
}
+ /**
+ * 显示Toast提示
+ *
+ * 显示指定文本的Toast提示消息。
+ *
+ * @param text 提示文本
+ */
+ private void showToast(String text) {
+ Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
+ }
+
/**
* 显示Toast提示
*
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
index 9fc2d88..cd1b38f 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
@@ -21,7 +21,7 @@ import net.micode.notes.tool.SearchHistoryManager;
import java.util.ArrayList;
import java.util.List;
-public class NoteSearchActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, NoteSearchAdapter.OnItemClickListener {
+public class NoteSearchActivity extends BaseActivity implements SearchView.OnQueryTextListener, NoteSearchAdapter.OnItemClickListener {
private SearchView mSearchView;
private RecyclerView mRecyclerView;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
index fa8b2de..863997c 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
@@ -3,13 +3,31 @@ package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.net.Uri;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
import android.widget.Toast;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.micode.notes.tool.ImageExportHelper;
+import net.micode.notes.tool.PdfExportHelper;
+
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.GravityCompat;
@@ -23,6 +41,7 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.NotesRepository;
import net.micode.notes.databinding.ActivityHomeBinding;
import net.micode.notes.sync.SyncManager;
+import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.SecurityManager;
import net.micode.notes.viewmodel.NotesListViewModel;
@@ -33,7 +52,7 @@ import net.micode.notes.viewmodel.NotesListViewModel;
* 使用 ViewBinding 访问视图。
*
*/
-public class NotesListActivity extends AppCompatActivity implements SidebarFragment.OnSidebarItemSelectedListener {
+public class NotesListActivity extends BaseActivity implements SidebarFragment.OnSidebarItemSelectedListener {
private static final String TAG = "NotesListActivity";
private ActivityHomeBinding binding;
@@ -137,6 +156,11 @@ public class NotesListActivity extends AppCompatActivity implements SidebarFragm
viewModel.setIsSelectionMode(false);
});
+ binding.btnActionExport.setOnClickListener(v -> {
+ exportSelectedNotes();
+ viewModel.setIsSelectionMode(false);
+ });
+
binding.btnActionLock.setOnClickListener(v -> {
SecurityManager securityManager = SecurityManager.getInstance(this);
if (!securityManager.isPasswordSet()) {
@@ -200,7 +224,7 @@ public class NotesListActivity extends AppCompatActivity implements SidebarFragm
new ViewModelProvider.Factory() {
@Override
public T create(Class modelClass) {
- return (T) new NotesListViewModel(repository);
+ return (T) new NotesListViewModel(getApplication(), repository);
}
}).get(NotesListViewModel.class);
@@ -250,6 +274,15 @@ public class NotesListActivity extends AppCompatActivity implements SidebarFragm
updateSelectionCount();
updateSelectionActionUI();
});
+
+ viewModel.getSidebarRefreshNeeded().observe(this, needed -> {
+ if (needed) {
+ SidebarFragment fragment = (SidebarFragment) getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment);
+ if (fragment != null) {
+ fragment.refreshFolderTree();
+ }
+ }
+ });
}
private void updateTrashModeUI(boolean isTrash) {
@@ -365,17 +398,257 @@ public class NotesListActivity extends AppCompatActivity implements SidebarFragm
@Override public void onSyncSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); SyncManager.getInstance().syncNotes(null); }
@Override public void onLoginSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); startActivity(new Intent(this, LoginActivity.class)); }
@Override public void onLogoutSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); viewModel.refreshNotes(); }
- @Override public void onExportSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); Toast.makeText(this, "导出功能待实现", Toast.LENGTH_SHORT).show(); }
+ @Override public void onExportSelected() {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
+ viewModel.setIsSelectionMode(true);
+ Toast.makeText(this, "请选择要导出的便签", Toast.LENGTH_SHORT).show();
+ }
@Override public void onTemplateSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); viewModel.enterFolder(Notes.ID_TEMPLATE_FOLDER); }
@Override public void onSettingsSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); startActivity(new Intent(this, SettingsActivity.class)); }
@Override public void onCapsuleSelected() {
binding.drawerLayout.closeDrawer(GravityCompat.START);
startActivity(new Intent(this, CapsuleListActivity.class));
}
- @Override public void onCreateFolder() { binding.drawerLayout.closeDrawer(GravityCompat.START); /* Show dialog */ }
+ @Override public void onCreateFolder() {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
+ SidebarFragment fragment = (SidebarFragment) getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment);
+ if (fragment != null) {
+ fragment.showCreateFolderDialog();
+ }
+ }
@Override public void onCloseSidebar() { binding.drawerLayout.closeDrawer(GravityCompat.START); }
- @Override public void onRenameFolder(long folderId) { /* Handle rename */ }
- @Override public void onDeleteFolder(long folderId) { /* Handle delete */ }
+ @Override public void onRenameFolder(long folderId) {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
+ // Show rename dialog
+ viewModel.getFolderInfo(folderId, new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(NotesRepository.NoteInfo folderInfo) {
+ runOnUiThread(() -> {
+ if (folderInfo != null) {
+ showRenameFolderDialog(folderId, folderInfo.snippet);
+ }
+ });
+ }
+
+ @Override
+ public void onError(Exception error) {
+ runOnUiThread(() -> Toast.makeText(NotesListActivity.this, "获取文件夹信息失败", Toast.LENGTH_SHORT).show());
+ }
+ });
+ }
+
+ private void exportSelectedNotes() {
+ java.util.List selectedIds = viewModel.getSelectedNoteIds();
+ if (selectedIds.isEmpty()) {
+ Toast.makeText(this, "请先选择要导出的便签", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("批量导出便签");
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(50, 20, 50, 20);
+
+ final RadioGroup group = new RadioGroup(this);
+ RadioButton rbText = new RadioButton(this);
+ rbText.setText("导出为文本 (.txt)");
+ rbText.setId(View.generateViewId());
+ group.addView(rbText);
+
+ RadioButton rbImage = new RadioButton(this);
+ rbImage.setText("导出为图片 (.png)");
+ rbImage.setId(View.generateViewId());
+ group.addView(rbImage);
+
+ RadioButton rbPdf = new RadioButton(this);
+ rbPdf.setText("导出为 PDF (.pdf)");
+ rbPdf.setId(View.generateViewId());
+ group.addView(rbPdf);
+
+ group.check(rbText.getId());
+ layout.addView(group);
+
+ final CheckBox cbShare = new CheckBox(this);
+ cbShare.setText("导出后立即分享");
+ layout.addView(cbShare);
+
+ builder.setView(layout);
+
+ builder.setPositiveButton("开始导出", (dialog, which) -> {
+ int checkedId = group.getCheckedRadioButtonId();
+ boolean share = cbShare.isChecked();
+
+ if (checkedId == rbText.getId()) {
+ performBatchExport(0, selectedIds, share);
+ } else if (checkedId == rbImage.getId()) {
+ performBatchExport(1, selectedIds, share);
+ } else if (checkedId == rbPdf.getId()) {
+ performBatchExport(2, selectedIds, share);
+ }
+ });
+
+ builder.setNegativeButton("取消", null);
+ builder.show();
+ }
+
+ private void performBatchExport(int format, java.util.List selectedIds, boolean share) {
+ if (format == 0) { // Text (Combined)
+ BackupUtils backupUtils = BackupUtils.getInstance(this);
+ int state = backupUtils.exportNotesToText(selectedIds);
+ if (state == BackupUtils.STATE_SUCCESS) {
+ File file = new File(backupUtils.getExportedTextFileDir(), backupUtils.getExportedTextFileName());
+ Toast.makeText(this, "已导出至下载目录: " + file.getName(), Toast.LENGTH_SHORT).show();
+ if (share) shareFile(file, "text/plain");
+ } else {
+ Toast.makeText(this, "导出失败", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ final ArrayList uris = new ArrayList<>();
+ final int total = selectedIds.size();
+ final int[] successCount = {0};
+ final String mimeType = (format == 1) ? "image/png" : "application/pdf";
+
+ for (Long id : selectedIds) {
+ viewModel.getNoteContent(id, new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(String content) {
+ if (!TextUtils.isEmpty(content)) {
+ String title = getTitleFromContent(content);
+ File exportedFile = null;
+ if (format == 1) { // Image
+ Bitmap bitmap = renderTextToBitmap(content);
+ Uri uri = ImageExportHelper.saveBitmapToExternal(NotesListActivity.this, bitmap, title);
+ if (uri != null) {
+ successCount[0]++;
+ uris.add(uri);
+ }
+ } else { // PDF
+ exportedFile = PdfExportHelper.exportToPdf(NotesListActivity.this, title, content);
+ if (exportedFile != null) {
+ successCount[0]++;
+ uris.add(androidx.core.content.FileProvider.getUriForFile(NotesListActivity.this, getPackageName() + ".fileprovider", exportedFile));
+ }
+ }
+ }
+ checkBatchFinished(successCount[0], total, uris, mimeType, share);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ checkBatchFinished(successCount[0], total, uris, mimeType, share);
+ }
+ });
+ }
+ }
+ }
+
+ private Bitmap renderTextToBitmap(String text) {
+ android.widget.TextView textView = new android.widget.TextView(this);
+ textView.setText(text);
+ textView.setTextColor(Color.BLACK);
+ textView.setBackgroundColor(Color.WHITE);
+ textView.setTextSize(16);
+ textView.setPadding(40, 40, 40, 40);
+ textView.setWidth(800);
+
+ int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(800, View.MeasureSpec.EXACTLY);
+ int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ textView.measure(widthMeasureSpec, heightMeasureSpec);
+ textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());
+
+ Bitmap bitmap = Bitmap.createBitmap(textView.getMeasuredWidth(), textView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ textView.draw(canvas);
+ return bitmap;
+ }
+
+ private synchronized void checkBatchFinished(int success, int total, ArrayList uris, String mimeType, boolean share) {
+ // Since we are doing this sequentially or with callbacks, we need to track progress
+ // For simplicity in this implementation, I'll just check if we have reached total count
+ if (uris.size() + (total - success) >= total) {
+ if (success > 0) {
+ Toast.makeText(this, "成功导出 " + success + " 个文件至下载目录", Toast.LENGTH_SHORT).show();
+ if (share) shareUris(uris, mimeType);
+ } else {
+ Toast.makeText(this, "导出失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private String getTitleFromContent(String content) {
+ if (TextUtils.isEmpty(content)) return "untitled";
+ String title = content.trim();
+ int firstNewLine = title.indexOf('\n');
+ if (firstNewLine > 0) {
+ title = title.substring(0, firstNewLine);
+ }
+ if (title.length() > 30) {
+ title = title.substring(0, 30);
+ }
+ return title;
+ }
+
+ private void shareFile(File file, String mimeType) {
+ Uri uri = androidx.core.content.FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(mimeType);
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(Intent.createChooser(intent, "分享便签"));
+ }
+
+ private void shareUris(ArrayList uris, String mimeType) {
+ Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ intent.setType(mimeType);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(Intent.createChooser(intent, "分享便签"));
+ }
+
+ private void showRenameFolderDialog(long folderId, String currentName) {
+ final EditText input = new EditText(this);
+ input.setText(currentName);
+ input.setSelection(currentName.length());
+
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_rename_folder_title)
+ .setView(input)
+ .setPositiveButton(R.string.menu_rename, (dialog, which) -> {
+ String newName = input.getText().toString().trim();
+ if (!newName.isEmpty()) {
+ viewModel.renameFolder(folderId, newName);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ @Override public void onDeleteFolder(long folderId) {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
+ // Show delete confirmation
+ viewModel.getFolderInfo(folderId, new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(NotesRepository.NoteInfo folderInfo) {
+ runOnUiThread(() -> {
+ if (folderInfo != null) {
+ new AlertDialog.Builder(NotesListActivity.this)
+ .setTitle(R.string.dialog_delete_folder_title)
+ .setMessage(String.format(getString(R.string.dialog_delete_folder_with_notes), folderInfo.snippet, folderInfo.notesCount))
+ .setPositiveButton(R.string.menu_delete, (dialog, which) -> viewModel.deleteFolder(folderId))
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+ });
+ }
+
+ @Override
+ public void onError(Exception error) {
+ runOnUiThread(() -> Toast.makeText(NotesListActivity.this, "获取文件夹信息失败", Toast.LENGTH_SHORT).show());
+ }
+ });
+ }
private class MainPagerAdapter extends FragmentStateAdapter {
public MainPagerAdapter(@NonNull AppCompatActivity activity) { super(activity); }
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java
index 2fca2c1..fe66a43 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java
@@ -66,7 +66,7 @@ public class NotesListFragment extends Fragment implements
new ViewModelProvider.Factory() {
@Override
public T create(Class modelClass) {
- return (T) new NotesListViewModel(repository);
+ return (T) new NotesListViewModel(requireActivity().getApplication(), repository);
}
}).get(NotesListViewModel.class);
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java
index de53616..e3b4290 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java
@@ -11,7 +11,7 @@ import androidx.appcompat.app.AppCompatActivity;
import net.micode.notes.R;
-public class NotesPreferenceActivity extends AppCompatActivity {
+public class NotesPreferenceActivity extends BaseActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
@@ -33,20 +33,6 @@ public class NotesPreferenceActivity extends AppCompatActivity {
.replace(R.id.settings_container, new SettingsFragment())
.commit();
}
-
- loadSyncButton();
- }
-
- private void loadSyncButton() {
- Button syncButton = findViewById(R.id.preference_sync_button);
- TextView lastSyncTimeView = findViewById(R.id.prefenerece_sync_status_textview);
-
- // Google Tasks同步功能已禁用
- syncButton.setEnabled(false);
- syncButton.setText("同步功能已禁用");
-
- lastSyncTimeView.setText("Google Tasks同步功能已禁用");
- lastSyncTimeView.setVisibility(View.VISIBLE);
}
public static String getSyncAccountName(android.content.Context context) {
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java
index 8f9b68c..b87bb75 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java
@@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import net.micode.notes.R;
-public class SettingsActivity extends AppCompatActivity {
+public class SettingsActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
index b8a9eda..0b4a9db 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
@@ -29,10 +29,15 @@ import net.micode.notes.capsule.ClipboardMonitorService;
import static android.app.Activity.RESULT_OK;
public class SettingsFragment extends PreferenceFragmentCompat {
- public static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
public static final String PREFERENCE_SECURITY_KEY = "pref_key_security";
public static final String PREFERENCE_THEME_MODE = "pref_theme_mode";
public static final String PREFERENCE_CAPSULE_ENABLE = "pref_key_capsule_enable";
+ public static final String PREFERENCE_FONT_SIZE = "pref_font_size";
+ public static final String PREFERENCE_BG_RANDOM_APPEAR = "pref_key_bg_random_appear";
+ public static final String PREFERENCE_LANGUAGE = "pref_language";
+ public static final String PREFERENCE_CLOUD_SYNC_KEY = "pref_key_cloud_sync";
+ public static final String PREFERENCE_EXPORT_NOTES_KEY = "pref_key_export_notes";
+
public static final int REQUEST_CODE_CHECK_PASSWORD = 104;
public static final int REQUEST_CODE_OVERLAY_PERMISSION = 105;
@@ -41,10 +46,77 @@ public class SettingsFragment extends PreferenceFragmentCompat {
setPreferencesFromResource(R.xml.preferences, rootKey);
loadThemePreference();
loadSecurityPreference();
- loadAccountPreference();
loadCapsulePreference();
+ loadCloudSyncPreference();
+ loadExportPreference();
+ loadFontSizePreference();
+ loadLanguagePreference();
+ }
+
+ private void loadLanguagePreference() {
+ ListPreference languagePref = findPreference(PREFERENCE_LANGUAGE);
+ if (languagePref != null) {
+ languagePref.setOnPreferenceChangeListener((preference, newValue) -> {
+ String language = (String) newValue;
+ net.micode.notes.tool.LocaleHelper.setLocale(getContext(), language);
+ // 重新启动应用或 Activity 以应用更改
+ getActivity().recreate();
+ return true;
+ });
+ }
}
+ private void loadFontSizePreference() {
+ ListPreference fontSizePref = findPreference(PREFERENCE_FONT_SIZE);
+ if (fontSizePref != null) {
+ fontSizePref.setOnPreferenceChangeListener((preference, newValue) -> {
+ // ListPreference 默认存储字符串,NoteEditActivity 需要整数
+ // 这里我们只需确保它能保存,NoteEditActivity 稍后会修改读取逻辑
+ return true;
+ });
+ }
+ }
+
+ private void loadCloudSyncPreference() {
+ Preference syncPref = findPreference(PREFERENCE_CLOUD_SYNC_KEY);
+ if (syncPref != null) {
+ syncPref.setOnPreferenceClickListener(preference -> {
+ Intent intent = new Intent(getActivity(), SyncActivity.class);
+ startActivity(intent);
+ return true;
+ });
+ }
+ }
+
+ private void loadExportPreference() {
+ Preference exportPref = findPreference(PREFERENCE_EXPORT_NOTES_KEY);
+ if (exportPref != null) {
+ exportPref.setOnPreferenceClickListener(preference -> {
+ exportNotes();
+ return true;
+ });
+ }
+ }
+
+ private void exportNotes() {
+ net.micode.notes.tool.BackupUtils backupUtils = net.micode.notes.tool.BackupUtils.getInstance(getContext());
+ int state = backupUtils.exportToText();
+ String message;
+ switch (state) {
+ case net.micode.notes.tool.BackupUtils.STATE_SUCCESS:
+ message = getString(R.string.success_sdcard_export) + ": " + backupUtils.getExportedTextFileDir() + backupUtils.getExportedTextFileName();
+ break;
+ case net.micode.notes.tool.BackupUtils.STATE_SD_CARD_UNMOUONTED:
+ message = getString(R.string.error_sdcard_unmounted);
+ break;
+ case net.micode.notes.tool.BackupUtils.STATE_SYSTEM_ERROR:
+ default:
+ message = getString(R.string.error_sdcard_export);
+ break;
+ }
+ Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
+ }
+
private void loadCapsulePreference() {
SwitchPreferenceCompat capsulePref = findPreference(PREFERENCE_CAPSULE_ENABLE);
if (capsulePref != null) {
@@ -183,21 +255,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
}
}
- private void loadAccountPreference() {
- androidx.preference.PreferenceCategory accountCategory = findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
- if (accountCategory != null) {
- accountCategory.removeAll();
- Preference accountPref = new Preference(getContext());
- accountPref.setTitle(getString(R.string.preferences_account_title));
- accountPref.setSummary(getString(R.string.preferences_account_summary));
- accountPref.setOnPreferenceClickListener(preference -> {
- Toast.makeText(getActivity(), "Google Tasks同步功能已禁用", Toast.LENGTH_SHORT).show();
- return true;
- });
- accountCategory.addPreference(accountPref);
- }
- }
-
private void showSetPasswordDialog() {
new AlertDialog.Builder(getActivity())
.setTitle("设置密码")
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java
index f61cd7d..faec658 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java
@@ -72,8 +72,8 @@ public class SidebarFragment extends Fragment {
private LinearLayout menuExport;
private LinearLayout menuSettings;
private LinearLayout menuCapsule;
- private LinearLayout menuLogin;
private LinearLayout menuLogout;
+ private View logoutDivider;
// 文件夹树
private LinearLayout folderTreeContainer;
@@ -173,8 +173,8 @@ public class SidebarFragment extends Fragment {
menuExport = view.findViewById(R.id.menu_export);
menuSettings = view.findViewById(R.id.menu_settings);
menuCapsule = view.findViewById(R.id.menu_capsule);
- menuLogin = view.findViewById(R.id.menu_login);
menuLogout = view.findViewById(R.id.menu_logout);
+ logoutDivider = view.findViewById(R.id.logout_divider);
// 文件夹树
folderTreeContainer = view.findViewById(R.id.folder_tree_container);
@@ -249,13 +249,6 @@ public class SidebarFragment extends Fragment {
});
}
- menuLogin.setOnClickListener(v -> {
- if (listener != null) {
- listener.onLoginSelected();
- listener.onCloseSidebar();
- }
- });
-
menuLogout.setOnClickListener(v -> showLogoutConfirmDialog());
}
@@ -323,10 +316,12 @@ public class SidebarFragment extends Fragment {
}
// 更新菜单项
- if (menuLogin != null && menuLogout != null) {
- menuLogin.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
+ if (menuLogout != null) {
menuLogout.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
}
+ if (logoutDivider != null) {
+ logoutDivider.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
+ }
}
private void showLogoutConfirmDialog() {
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SyncActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SyncActivity.java
index 63c0391..66282db 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SyncActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SyncActivity.java
@@ -42,7 +42,7 @@ import java.util.Locale;
* 显示云同步设置,包括登录状态、同步开关、同步按钮和进度显示。
*
*/
-public class SyncActivity extends AppCompatActivity {
+public class SyncActivity extends BaseActivity {
private static final String PREFS_SYNC = "sync_settings";
private static final String KEY_AUTO_SYNC = "auto_sync";
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskEditActivity.java
index 8960a57..2ff4846 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskEditActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskEditActivity.java
@@ -24,7 +24,7 @@ import net.micode.notes.model.Task;
import java.util.Calendar;
-public class TaskEditActivity extends AppCompatActivity {
+public class TaskEditActivity extends BaseActivity {
private EditText contentEdit;
private ImageView alarmBtn;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
index 9b965d4..4a940c8 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
@@ -22,7 +22,7 @@ import net.micode.notes.model.Task;
import java.util.ArrayList;
import java.util.List;
-public class TaskListActivity extends AppCompatActivity implements TaskListAdapter.OnTaskItemClickListener {
+public class TaskListActivity extends BaseActivity implements TaskListAdapter.OnTaskItemClickListener {
private RecyclerView recyclerView;
private TaskListAdapter adapter;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
index 2b051dd..0307c24 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
@@ -16,11 +16,15 @@
package net.micode.notes.viewmodel;
+import android.app.Application;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
+import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.NotesRepository;
@@ -38,7 +42,7 @@ import java.util.List;
* @see NotesRepository
* @see net.micode.notes.model.Note
*/
-public class NotesListViewModel extends ViewModel {
+public class NotesListViewModel extends AndroidViewModel {
private static final String TAG = "NotesListViewModel";
private final NotesRepository repository;
@@ -80,9 +84,11 @@ public class NotesListViewModel extends ViewModel {
/**
* 构造函数
*
+ * @param application 应用程序上下文
* @param repository 笔记数据仓库
*/
- public NotesListViewModel(NotesRepository repository) {
+ public NotesListViewModel(@NonNull Application application, NotesRepository repository) {
+ super(application);
this.repository = repository;
Log.d(TAG, "ViewModel created");
}
@@ -185,7 +191,7 @@ public class NotesListViewModel extends ViewModel {
// 1. "All" / "All Templates" Folder (Virtual)
NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
allFolder.setId(templateMode ? Notes.ID_TEMPLATE_FOLDER : Notes.ID_ALL_NOTES_FOLDER);
- allFolder.snippet = templateMode ? "所有模板" : "所有"; // Name
+ allFolder.snippet = getApplication().getString(templateMode ? R.string.folder_all_templates : R.string.folder_all); // Name
displayFolders.add(allFolder);
// 2. Real Folders (from DB)
@@ -197,7 +203,7 @@ public class NotesListViewModel extends ViewModel {
if (!templateMode) {
NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
- uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
+ uncategorizedFolder.snippet = getApplication().getString(R.string.folder_uncategorized); // Custom Name for Root
displayFolders.add(uncategorizedFolder);
}
@@ -936,6 +942,26 @@ public class NotesListViewModel extends ViewModel {
});
}
+ /**
+ * 获取单个笔记的详细信息
+ *
+ * @param noteId 笔记ID
+ * @param callback 回调接口
+ */
+ public void getNoteInfo(long noteId, NotesRepository.Callback callback) {
+ repository.getNoteInfo(noteId, callback);
+ }
+
+ /**
+ * 获取单个笔记的完整内容
+ *
+ * @param noteId 笔记ID
+ * @param callback 回调接口
+ */
+ public void getNoteContent(long noteId, NotesRepository.Callback callback) {
+ repository.getNoteContent(noteId, callback);
+ }
+
/**
* 获取文件夹信息
*
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_home.xml b/src/Notesmaster/app/src/main/res/layout/activity_home.xml
index 1a3cd5a..675d757 100644
--- a/src/Notesmaster/app/src/main/res/layout/activity_home.xml
+++ b/src/Notesmaster/app/src/main/res/layout/activity_home.xml
@@ -77,7 +77,7 @@
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
- android:text="Search notes"
+ android:text="@string/search_hint"
android:textColor="#9E9E9E"
android:textSize="16sp" />
@@ -196,6 +196,7 @@
+
diff --git a/src/Notesmaster/app/src/main/res/layout/add_account_text.xml b/src/Notesmaster/app/src/main/res/layout/add_account_text.xml
deleted file mode 100644
index c799178..0000000
--- a/src/Notesmaster/app/src/main/res/layout/add_account_text.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml b/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
index 34683d4..eb2ab76 100644
--- a/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
+++ b/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
@@ -303,65 +303,46 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/Notesmaster/app/src/main/res/menu/note_edit.xml b/src/Notesmaster/app/src/main/res/menu/note_edit.xml
index 64e0fcc..ea281a5 100644
--- a/src/Notesmaster/app/src/main/res/menu/note_edit.xml
+++ b/src/Notesmaster/app/src/main/res/menu/note_edit.xml
@@ -71,6 +71,10 @@
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
+
+
diff --git a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
index ac490e5..27f798c 100644
--- a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
@@ -1,25 +1,9 @@
-
-
-
便签
- 便签2x2
- 便签4x4
+ 便签 2x2
+ 便签 4x4
没有关联内容,点击新建便签。
访客模式下,便签内容不可见
...
@@ -35,7 +19,13 @@
发送邮件
浏览网页
打开地图
-
+ 创建闹钟
+ 查看地图
+
+ /MIUI/notes/
+ notes_%s.txt
+
+ (%d)
新建文件夹
导出文本
同步
@@ -44,6 +34,7 @@
搜索
删除
移动到文件夹
+ 重命名
选中了 %d 项
没有选中项,操作无效
全选
@@ -56,9 +47,17 @@
进入清单模式
退出清单模式
查看文件夹
- 刪除文件夹
+ 删除文件夹
修改文件夹名称
文件夹 %1$s 已存在,请重新命名
+ 待办事项
+
+
+ 重命名文件夹
+ 删除文件夹
+ 删除 \"%1$s\" 及其 %2$d 条笔记?
+ 删除 \"%1$s\"?
+ 文件夹名称
分享
发送到桌面
提醒我
@@ -66,17 +65,19 @@
选择文件夹
上一级文件夹
已添加到桌面
- 删除
+ 确认删除文件夹及所包含的便签吗?
+ 删除所选便签
确认要删除所选的 %d 条便签吗?
确认要删除该条便签吗?
- 确认删除文件夹及所包含的便签吗?
已将所选 %1$d 条便签移到 %2$s 文件夹
-
+
SD卡被占用,不能操作
导出文本时发生错误,请检查SD卡
要查看的便签不存在
不能为空便签设置闹钟提醒
不能将空便签发送到桌面
+ 无效的意图
+ 不支持的意图操作
导出成功
导出失败
已将文本文件(%1$s)输出至SD卡(%2$s)目录
@@ -91,28 +92,21 @@
同步已取消
登录%1$s...
正在获取服务器便签列表...
- 正在同步本地便签...
设置
- 同步账号
- 与google task同步便签记录
- 上次同步于 %1$s
- 添加账号
- 更换账号
- 删除账号
- 取消
+ yyyy-MM-dd hh:mm:ss
立即同步
取消同步
- 当前帐号 %1$s
- 如更换同步帐号,过去的帐号同步信息将被清空,再次切换的同时可能会造成数据重复
- 同步便签
- 请选择google帐号,便签将与该帐号的google task内容同步。
- 正在同步中,不能修改同步帐号
- 同步帐号已设置为%1$s
新建便签背景颜色随机
+ 语言
+ 所有
+ 所有模板
+ 未分类
+
删除
通话便签
请输入名称
+
正在搜索便签
搜索便签
便签中的文字
@@ -123,50 +117,115 @@
- %1$s 条符合"%2$s"的搜索结果
-
- 我的便签
- %d 个便签
- 创建文件夹
- 文件夹名称
- 文件夹名称不能为空
- 文件夹名称过长(最多50个字符)
- 回收站
- 创建文件夹成功
-
- 撤回
- 重做
- 清空撤回历史
- 撤回成功
- 重做成功
- 无可撤回
- 无可重做
-
- 重命名
- 重命名文件夹
- 删除文件夹
- 删除 "%1$s" 及其 %2$d 条笔记?
- 删除 "%1$s"?
- 文件夹名称
- 无效的意图
- 不支持的意图操作
暂无便签,点击右下角按钮创建
空便签图标
编辑便签
+
登录
导出
+ 模板
设置
+ 回收站
+ 我的便签
关闭侧边栏
创建文件夹
+ %d 个便签
+ 创建文件夹
+ 文件夹名称
+ 文件夹名称不能为空
+ 文件夹名称过长(最多50个字符)
文件夹已存在
+ 创建文件夹成功
置顶
锁定
确定需要为其上锁?
确定
再想想
+
确定要删除选中的便签吗?
取消置顶
解锁
+
+
+ 未找到结果
+ 搜索历史
+ 清空
+
+
+ 撤销
+ 重做
+ 清空撤销历史
+ 撤销成功
+ 重做成功
+ 无可撤销内容
+ 无可重做内容
+ 保存为模板
+ 图片
+ 富文本
+
+
+ 小米便签全局速记服务。开启后请勿开启“无障碍快捷方式”,以免出现多余的系统悬浮按钮。
+ 需要悬浮窗权限
+ 全局速记胶囊需要悬浮窗权限才能显示在其他应用上层。
+ 需要无障碍服务权限
+ 全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。
+ 全局速记胶囊
+ 开启侧边悬浮胶囊,支持跨应用拖拽和剪贴板速记
恢复
永久删除
+
+ 点击登录/注册
+ 用户
+ 设备 ID: 未知
+ 已同步
+ 同步中...
+ 同步失败
+ 笔记
+ 云同步
+ 其他
+ 全部笔记
+ 收藏
+ 提醒
+ 立即同步
+ 同步设置
+ 帮助与反馈
+ 登录
+ 退出登录
+
+
+ 云同步
+ 登录状态
+ 设备 ID: 未初始化
+ 自动同步
+ 最后同步时间:
+ 从不
+ 同步状态:
+ 空闲
+ 同步中...
+ 同步成功
+ 同步失败
+ 立即同步
+ 同步已开始
+ 同步成功完成
+ 同步失败: %1$s
+
+
+ 退出登录
+ 确定要退出登录吗?退出后本地笔记将保留,但无法同步到云端。
+ 退出
+ 已退出登录
+
+
+ 文件夹
+ 创建文件夹失败
+
+
+ 笔记冲突
+ 本地版本
+ 云端版本
+ 使用本地
+ 使用云端
+ 合并
+ 合并功能即将推出
diff --git a/src/Notesmaster/app/src/main/res/values-zh-rTW/strings.xml b/src/Notesmaster/app/src/main/res/values-zh-rTW/strings.xml
index e17bfdd..5efe94d 100644
--- a/src/Notesmaster/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values-zh-rTW/strings.xml
@@ -1,29 +1,13 @@
-
-
-
-
+
- 便簽
- 便簽2x2
- 便簽4x4
- 沒有關聯內容,點擊新建便簽。
+ 便籤
+ 便籤 2x2
+ 便籤 4x4
+ 沒有關聯內容,點擊新建便籤。
訪客模式下,便籤內容不可見
...
- 新建便簽
+ 新建便籤
成功刪除提醒
創建提醒
已過期
@@ -33,18 +17,24 @@
查看
呼叫電話
發送郵件
- 浏覽網頁
+ 瀏覽網頁
打開地圖
- 已將所選 %1$d 便籤移到 %2$s 文件夾
-
+ 創建鬧鐘
+ 查看地圖
+
+ /MIUI/notes/
+ notes_%s.txt
+
+ (%d)
新建文件夾
導出文本
同步
取消同步
設置
- 搜尋
+ 搜索
刪除
移動到文件夾
+ 重命名
選中了 %d 項
沒有選中項,操作無效
全選
@@ -60,6 +50,14 @@
刪除文件夾
修改文件夾名稱
文件夾 %1$s 已存在,請重新命名
+ 待辦事項
+
+
+ 重命名文件夾
+ 刪除文件夾
+ 刪除 \"%1$s\" 及其 %2$d 條筆記?
+ 刪除 \"%1$s\"?
+ 文件夾名稱
分享
發送到桌面
提醒我
@@ -67,20 +65,24 @@
選擇文件夾
上一級文件夾
已添加到桌面
- 刪除
- 确认要刪除所選的 %d 條便籤嗎?
- 确认要删除該條便籤嗎?
- 確認刪除檔夾及所包含的便簽嗎?
+ 確認刪除文件夾及所包含的便籤嗎?
+ 刪除所選便籤
+ 確認要刪除所選的 %d 條便籤嗎?
+ 確認要刪除該條便籤嗎?
+ 已將所選 %1$d 條便籤移到 %2$s 文件夾
+
SD卡被佔用,不能操作
- 導出TXT時發生錯誤,請檢查SD卡
+ 導出文本時發生錯誤,請檢查SD卡
要查看的便籤不存在
- 不能爲空便籤設置鬧鐘提醒
+ 不能空便籤設置鬧鐘提醒
不能將空便籤發送到桌面
+ 無效的意圖
+ 不支持的意圖操作
導出成功
導出失敗
- 已將文本文件(%1$s)導出至SD(%2$s)目錄
+ 已將文本文件(%1$s)輸出至SD卡(%2$s)目錄
- 同步便簽...
+ 同步便籤...
同步成功
同步失敗
同步已取消
@@ -88,27 +90,18 @@
同步失敗,請檢查網絡和帳號設置
同步失敗,發生內部錯誤
同步已取消
- 登陸%1$s...
+ 登錄%1$s...
正在獲取服務器便籤列表...
- 正在同步本地便籤...
設置
- 同步賬號
- 与google task同步便簽記錄
- 上次同步于 %1$s
- 添加賬號
- 更換賬號
- 刪除賬號
- 取消
+ yyyy-MM-dd hh:mm:ss
立即同步
取消同步
- 當前帳號 %1$s
- 如更換同步帳號,過去的帳號同步信息將被清空,再次切換的同時可能會造成數據重復
- 同步便簽
- 請選擇google帳號,便簽將與該帳號的google task內容同步。
- 正在同步中,不能修改同步帳號
- 同步帳號已設置為%1$s
新建便籤背景顏色隨機
+ 語言
+ 所有
+ 所有模板
+ 未分類
刪除
通話便籤
@@ -121,46 +114,118 @@
設置
取消
- - %1$s 條符合"%2$s"的搜尋結果
+ - %1$s 條符合"%2$s"的搜索結果
-
- 我的便籤
- %d 個便籤
- 創建文件夾
- 文件夾名稱
- 文件夾名稱不能為空
- 文件夾名稱過長(最多50個字符)
- 回收站
- 創建文件夾成功
-
-
- 重命名
- 重命名文件夾
- 刪除文件夾
- 刪除 "%1$s" 及其 %2$d 條筆記?
- 刪除 "%1$s"?
- 文件夾名稱
- 無效的意圖
- 不支持的意圖操作
暫無便籤,點擊右下角按鈕創建
空便籤圖標
編輯便籤
+
登錄
導出
+ 模板
設置
+ 回收站
+ 我的便籤
關閉側邊欄
創建文件夾
+ %d 個便籤
+ 創建文件夾
+ 文件夾名稱
+ 文件夾名稱不能為空
+ 文件夾名稱過長(最多50個字符)
文件夾已存在
+ 創建文件夾成功
置頂
鎖定
確定需要為其上鎖?
確定
再想想
+
確定要刪除選中的便籤嗎?
取消置頂
解鎖
+
+
+ 未找到結果
+ 搜索歷史
+ 清空
+
+
+ 撤銷
+ 重做
+ 清空撤銷歷史
+ 撤銷成功
+ 重做成功
+ 無可撤銷內容
+ 無可重做內容
+ 保存為模板
+ 圖片
+ 富文本
+
+
+ 小米便籤全局速記服務。開啟後請勿開啟“無障礙快捷方式”,以免出現多餘的系統懸浮按鈕。
+ 需要懸浮窗權限
+ 全局速記膠囊需要懸浮窗權限才能顯示在其他應用上層。
+ 需要無障礙服務權限
+ 全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。
+ 全局速記膠囊
+ 開啟側邊懸浮膠囊,支持跨應用拖拽和剪貼板速記
恢復
永久刪除
+
+ 點擊登錄/註冊
+ 用戶
+ 設備 ID: 未知
+ 已同步
+ 同步中...
+ 同步失敗
+ 筆記
+ 雲同步
+ 其他
+ 全部筆記
+ 收藏
+ 提醒
+ 立即同步
+ 同步設置
+ 幫助與反饋
+ 登錄
+ 退出登錄
+
+
+ 雲同步
+ 登錄狀態
+ 設備 ID: 未初始化
+ 自動同步
+ 最後同步時間:
+ 從不
+ 同步狀態:
+ 空閒
+ 同步中...
+ 同步成功
+ 同步失敗
+ 立即同步
+ 同步已開始
+ 同步成功完成
+ 同步失敗: %1$s
+
+
+ 退出登錄
+ 確定要退出登錄嗎?退出後本地筆記將保留,但無法同步到雲端。
+ 退出
+ 已退出登錄
+
+
+ 文件夾
+ 創建文件夾失敗
+
+
+ 筆記衝突
+ 本地版本
+ 雲端版本
+ 使用本地
+ 使用雲端
+ 合併
+ 合併功能即將推出
diff --git a/src/Notesmaster/app/src/main/res/values/arrays.xml b/src/Notesmaster/app/src/main/res/values/arrays.xml
index ce7ac3a..0f7f2ca 100644
--- a/src/Notesmaster/app/src/main/res/values/arrays.xml
+++ b/src/Notesmaster/app/src/main/res/values/arrays.xml
@@ -40,4 +40,32 @@
- light
- dark
+
+
+ - @string/menu_font_small
+ - @string/menu_font_normal
+ - @string/menu_font_large
+ - @string/menu_font_super
+
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+
+
+
+ - 简体中文
+ - 繁體中文
+ - English
+ - System Default
+
+
+
+ - zh-CN
+ - zh-TW
+ - en
+ - system
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml
index 053299e..5d1b869 100644
--- a/src/Notesmaster/app/src/main/res/values/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values/strings.xml
@@ -108,26 +108,16 @@
Sync is canceled
Logging into %1$s...
Getting remote note list...
- Synchronize local notes with Google Task...
Settings
- Sync account
- Sync notes with google task
- Last sync time %1$s
yyyy-MM-dd hh:mm:ss
- Add account
- Change sync account
- Remove sync account
- Cancel
Sync immediately
Cancel syncing
- Current account %1$s
- All sync related information will be deleted, which may result in duplicated items sometime
- Sync notes
- Please select a google account. Local notes will be synced with google task.
- Cannot change the account because sync is in progress
- %1$s has been set as the sync account
New note background color random
+ Language
+ All
+ All Templates
+ Uncategorized
Delete
Call notes
diff --git a/src/Notesmaster/app/src/main/res/xml/file_paths.xml b/src/Notesmaster/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..d68a56c
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Notesmaster/app/src/main/res/xml/preferences.xml b/src/Notesmaster/app/src/main/res/xml/preferences.xml
index b1a3b16..50c575d 100644
--- a/src/Notesmaster/app/src/main/res/xml/preferences.xml
+++ b/src/Notesmaster/app/src/main/res/xml/preferences.xml
@@ -6,6 +6,25 @@
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
android:defaultValue="system" />
+
+
+
+
+
+
@@ -23,14 +42,21 @@
android:defaultValue="false" />
-
-
+
+
+
+
+ android:summary="1.1.0" />