进行导出、语言设置等功能完善 #34

Merged
mbls3xqnp merged 2 commits from baoerjun_branch into master 3 weeks ago

@ -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" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="net.micode.notes.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- ==================== 桌面小部件接收器 ==================== -->
<!-- 2x2大小的笔记桌面小部件 -->
<receiver

@ -5,6 +5,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
@ -15,6 +16,8 @@ import androidx.drawerlayout.widget.DrawerLayout;
import net.micode.notes.data.Notes;
import net.micode.notes.databinding.ActivityMainBinding;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.ui.BaseActivity;
import net.micode.notes.ui.SidebarFragment;
/**
@ -24,7 +27,7 @@ import net.micode.notes.ui.SidebarFragment;
*
* </p>
*/
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

@ -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();

@ -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<NoteInfo> 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<String> 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(

@ -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<Long> 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<Long> 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;
}
/**
*
* <p>
@ -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;
}
/**
*
* <p>
@ -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;
}
/**
*
* <p>
*
* </p>
*
* @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()) {
// 创建文件

@ -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;
/**
*
* <p>
* View
* </p>
*/
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;
}
}

@ -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);
}
}

@ -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
* <p>
* 使 Android PdfDocument API PDF
* </p>
*/
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;
}
}

@ -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;
// 笔记内容摘要

@ -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));
}
}

@ -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) {

@ -43,7 +43,7 @@ import net.micode.notes.viewmodel.LoginViewModel;
* MVVM {@link LoginViewModel}
* </p>
*/
public class LoginActivity extends AppCompatActivity {
public class LoginActivity extends BaseActivity {
private static final String TAG = "LoginActivity";

@ -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, "分享便签"));
}
/**
*
* <p>
@ -1742,6 +1890,17 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
}
/**
* Toast
* <p>
* Toast
* </p>
* @param text
*/
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
/**
* Toast
* <p>

@ -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;

@ -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 访
* </p>
*/
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 extends androidx.lifecycle.ViewModel> T create(Class<T> 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<NotesRepository.NoteInfo>() {
@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<Long> 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<Long> 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<Uri> 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<String>() {
@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<Uri> 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<Uri> 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<NotesRepository.NoteInfo>() {
@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); }

@ -66,7 +66,7 @@ public class NotesListFragment extends Fragment implements
new ViewModelProvider.Factory() {
@Override
public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass) {
return (T) new NotesListViewModel(repository);
return (T) new NotesListViewModel(requireActivity().getApplication(), repository);
}
}).get(NotesListViewModel.class);
}

@ -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) {

@ -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);

@ -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("设置密码")

@ -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() {

@ -42,7 +42,7 @@ import java.util.Locale;
*
* </p>
*/
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";

@ -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;

@ -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;

@ -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<NotesRepository.NoteInfo> callback) {
repository.getNoteInfo(noteId, callback);
}
/**
*
*
* @param noteId ID
* @param callback
*/
public void getNoteContent(long noteId, NotesRepository.Callback<String> callback) {
repository.getNoteContent(noteId, callback);
}
/**
*
* <p>

@ -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 @@
<TextView android:id="@+id/btn_action_hide" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="隐藏" android:drawableTop="@android:drawable/ic_menu_close_clear_cancel" android:gravity="center" android:visibility="gone"/>
<TextView android:id="@+id/btn_action_pin" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="置顶" android:drawableTop="@android:drawable/ic_menu_upload" android:gravity="center"/>
<TextView android:id="@+id/btn_action_export" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="导出" android:drawableTop="@android:drawable/ic_menu_save" android:gravity="center"/>
<TextView android:id="@+id/btn_action_move" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="移动到" android:drawableTop="@drawable/ic_folder" android:gravity="center"/>
<TextView android:id="@+id/btn_action_lock" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="加锁" android:drawableTop="@android:drawable/ic_lock_lock" android:gravity="center"/>
<TextView android:id="@+id/btn_action_delete" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="删除" android:drawableTop="@android:drawable/ic_menu_delete" android:gravity="center"/>

@ -1,32 +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.
-->
<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>

@ -303,65 +303,46 @@
</LinearLayout>
<!-- 登录 -->
<LinearLayout
android:id="@+id/menu_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:background="?attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_login"
app:tint="?attr/colorOnSurfaceVariant" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:text="@string/drawer_login"
android:textSize="14sp"
android:textColor="?attr/colorOnSurface" />
</LinearLayout>
<!-- 退出登录(默认隐藏)-->
<LinearLayout
android:id="@+id/menu_logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:background="?attr/selectableItemBackground"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_logout"
app:tint="?attr/colorOnSurfaceVariant" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:text="@string/drawer_logout"
android:textSize="14sp"
android:textColor="?attr/colorOnSurface" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<!-- 分隔线 -->
<View
android:id="@+id/logout_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorOutlineVariant"
android:layout_marginHorizontal="16dp"
android:visibility="gone" />
<!-- 退出登录(固定底部)-->
<LinearLayout
android:id="@+id/menu_logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:background="?attr/selectableItemBackground"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_logout"
app:tint="?attr/colorError" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:text="@string/drawer_logout"
android:textSize="14sp"
android:textColor="?attr/colorError" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</LinearLayout>

@ -71,6 +71,10 @@
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
<item
android:id="@+id/menu_export"
android:title="导出" />
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>

@ -1,25 +1,9 @@
<?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 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="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="notelist_string_info">...</string>
@ -35,7 +19,13 @@
<string name="note_link_email">发送邮件</string>
<string name="note_link_web">浏览网页</string>
<string name="note_link_other">打开地图</string>
<!-- note list string -->
<string name="note_link_time">创建闹钟</string>
<string name="note_link_geo">查看地图</string>
<!-- Text export file information -->
<string name="file_path" translatable="false">/MIUI/notes/</string>
<string name="file_name_txt_format" translatable="false">notes_%s.txt</string>
<!-- notes list string -->
<string name="format_folder_files_count" translatable="false">(%d)</string>
<string name="menu_create_folder">新建文件夹</string>
<string name="menu_export_text">导出文本</string>
<string name="menu_sync">同步</string>
@ -44,6 +34,7 @@
<string name="menu_search">搜索</string>
<string name="menu_delete">删除</string>
<string name="menu_move">移动到文件夹</string>
<string name="menu_rename">重命名</string>
<string name="menu_select_title">选中了 %d 项</string>
<string name="menu_select_none">没有选中项,操作无效</string>
<string name="menu_select_all">全选</string>
@ -56,9 +47,17 @@
<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_delete">除文件夹</string>
<string name="menu_folder_change_name">修改文件夹名称</string>
<string name="folder_exist">文件夹 %1$s 已存在,请重新命名</string>
<string name="menu_tasks">待办事项</string>
<!-- Folder operation dialogs -->
<string name="dialog_rename_folder_title">重命名文件夹</string>
<string name="dialog_delete_folder_title">删除文件夹</string>
<string name="dialog_delete_folder_with_notes">删除 \"%1$s\" 及其 %2$d 条笔记?</string>
<string name="dialog_delete_folder_empty">删除 \"%1$s\"?</string>
<string name="folder_name_hint">文件夹名称</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">发送到桌面</string>
<string name="menu_alert">提醒我</string>
@ -66,17 +65,19 @@
<string name="menu_title_select_folder">选择文件夹</string>
<string name="menu_move_parent_folder">上一级文件夹</string>
<string name="info_note_enter_desktop">已添加到桌面</string>
<string name="alert_title_delete">删除</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="alert_message_delete_folder">确认删除文件夹及所包含的便签吗?</string>
<string name="format_move_notes_to_folder">已将所选 %1$d 条便签移到 %2$s 文件夹</string>
<!-- export text -->
<!-- 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="error_intent_invalid">无效的意图</string>
<string name="error_intent_unsupported">不支持的意图操作</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>
@ -91,28 +92,21 @@
<string name="error_sync_cancelled">同步已取消</string>
<string name="sync_progress_login">登录%1$s...</string>
<string name="sync_progress_init_list">正在获取服务器便签列表...</string>
<string name="sync_progress_syncing">正在同步本地便签...</string>
<!-- Preferences -->
<string name="preferences_title">设置</string>
<string name="preferences_account_title">同步账号</string>
<string name="preferences_account_summary">与google task同步便签记录</string>
<string name="preferences_last_sync_time">上次同步于 %1$s</string>
<string name="preferences_add_account">添加账号</string>
<string name="preferences_menu_change_account">更换账号</string>
<string name="preferences_menu_remove_account">删除账号</string>
<string name="preferences_menu_cancel">取消</string>
<string name="preferences_last_sync_time_format" translatable="false">yyyy-MM-dd hh:mm:ss</string>
<string name="preferences_button_sync_immediately">立即同步</string>
<string name="preferences_button_sync_cancel">取消同步</string>
<string name="preferences_dialog_change_account_title">当前帐号 %1$s</string>
<string name="preferences_dialog_change_account_warn_msg">如更换同步帐号,过去的帐号同步信息将被清空,再次切换的同时可能会造成数据重复</string>
<string name="preferences_dialog_select_account_title">同步便签</string>
<string name="preferences_dialog_select_account_tips">请选择google帐号便签将与该帐号的google task内容同步。</string>
<string name="preferences_toast_cannot_change_account">正在同步中,不能修改同步帐号</string>
<string name="preferences_toast_success_set_accout">同步帐号已设置为%1$s</string>
<string name="preferences_bg_random_appear_title">新建便签背景颜色随机</string>
<string name="preferences_language_title">语言</string>
<string name="folder_all">所有</string>
<string name="folder_all_templates">所有模板</string>
<string name="folder_uncategorized">未分类</string>
<string name="button_delete">删除</string>
<string name="call_record_folder_name">通话便签</string>
<string name="hint_foler_name">请输入名称</string>
<string name="search_label">正在搜索便签</string>
<string name="search_hint">搜索便签</string>
<string name="search_setting_description">便签中的文字</string>
@ -123,50 +117,115 @@
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 条符合"<xliff:g id="SEARCH">%2$s</xliff:g>"的搜索结果</item>
</plurals>
<!-- Sidebar -->
<string name="root_folder_name">我的便签</string>
<string name="folder_note_count">%d 个便签</string>
<string name="dialog_create_folder_title">创建文件夹</string>
<string name="dialog_create_folder_hint">文件夹名称</string>
<string name="error_folder_name_empty">文件夹名称不能为空</string>
<string name="error_folder_name_too_long">文件夹名称过长最多50个字符</string>
<string name="menu_trash">回收站</string>
<string name="create_folder_success">创建文件夹成功</string>
<string name="menu_undo">撤回</string>
<string name="menu_redo">重做</string>
<string name="menu_clear_history">清空撤回历史</string>
<string name="undo_success">撤回成功</string>
<string name="redo_success">重做成功</string>
<string name="undo_fail">无可撤回</string>
<string name="redo_fail">无可重做</string>
<!-- New translations for missing strings -->
<string name="menu_rename">重命名</string>
<string name="dialog_rename_folder_title">重命名文件夹</string>
<string name="dialog_delete_folder_title">删除文件夹</string>
<string name="dialog_delete_folder_with_notes">删除 "%1$s" 及其 %2$d 条笔记?</string>
<string name="dialog_delete_folder_empty">删除 "%1$s"?</string>
<string name="folder_name_hint">文件夹名称</string>
<string name="error_intent_invalid">无效的意图</string>
<string name="error_intent_unsupported">不支持的意图操作</string>
<string name="empty_notes_hint">暂无便签,点击右下角按钮创建</string>
<string name="empty_notes_icon">空便签图标</string>
<string name="menu_edit_note">编辑便签</string>
<!-- Sidebar strings -->
<string name="menu_login">登录</string>
<string name="menu_export">导出</string>
<string name="menu_templates">模板</string>
<string name="menu_settings">设置</string>
<string name="menu_trash">回收站</string>
<string name="root_folder_name">我的便签</string>
<string name="sidebar_close">关闭侧边栏</string>
<string name="sidebar_create_folder">创建文件夹</string>
<string name="folder_note_count">%d 个便签</string>
<string name="dialog_create_folder_title">创建文件夹</string>
<string name="dialog_create_folder_hint">文件夹名称</string>
<string name="error_folder_name_empty">文件夹名称不能为空</string>
<string name="error_folder_name_too_long">文件夹名称过长最多50个字符</string>
<string name="error_folder_name_exists">文件夹已存在</string>
<string name="create_folder_success">创建文件夹成功</string>
<string name="menu_pin">置顶</string>
<string name="menu_lock">锁定</string>
<string name="lock_confirmation">确定需要为其上锁?</string>
<string name="lock_confirm_button">确定</string>
<string name="lock_cancel_button">再想想</string>
<string name="delete_confirmation">确定要删除选中的便签吗?</string>
<string name="menu_unpin">取消置顶</string>
<string name="menu_unlock">解锁</string>
<!-- Search related -->
<string name="search_no_results">未找到结果</string>
<string name="search_history_title">搜索历史</string>
<string name="search_history_clear">清空</string>
<!-- Undo/Redo -->
<string name="menu_undo">撤销</string>
<string name="menu_redo">重做</string>
<string name="menu_clear_history">清空撤销历史</string>
<string name="undo_success">撤销成功</string>
<string name="redo_success">重做成功</string>
<string name="undo_fail">无可撤销内容</string>
<string name="redo_fail">无可重做内容</string>
<string name="menu_save_as_template">保存为模板</string>
<string name="menu_picture">图片</string>
<string name="menu_rich_text">富文本</string>
<!-- Capsule Feature -->
<string name="accessibility_service_description">小米便签全局速记服务。开启后请勿开启“无障碍快捷方式”,以免出现多余的系统悬浮按钮。</string>
<string name="capsule_permission_alert_window_title">需要悬浮窗权限</string>
<string name="capsule_permission_alert_window_message">全局速记胶囊需要悬浮窗权限才能显示在其他应用上层。</string>
<string name="capsule_permission_accessibility_title">需要无障碍服务权限</string>
<string name="capsule_permission_accessibility_message">全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。</string>
<string name="preferences_capsule_title">全局速记胶囊</string>
<string name="preferences_capsule_summary">开启侧边悬浮胶囊,支持跨应用拖拽和剪贴板速记</string>
<string name="menu_restore">恢复</string>
<string name="menu_permanent_delete">永久删除</string>
<!-- Drawer Navigation -->
<string name="drawer_login_prompt">点击登录/注册</string>
<string name="drawer_default_username">用户</string>
<string name="drawer_default_device_id">设备 ID: 未知</string>
<string name="drawer_sync_status_synced">已同步</string>
<string name="drawer_sync_status_syncing">同步中...</string>
<string name="drawer_sync_status_failed">同步失败</string>
<string name="drawer_group_notes">笔记</string>
<string name="drawer_group_sync">云同步</string>
<string name="drawer_group_other">其他</string>
<string name="drawer_all_notes">全部笔记</string>
<string name="drawer_favorites">收藏</string>
<string name="drawer_reminders">提醒</string>
<string name="drawer_sync_now">立即同步</string>
<string name="drawer_sync_settings">同步设置</string>
<string name="drawer_help">帮助与反馈</string>
<string name="drawer_login">登录</string>
<string name="drawer_logout">退出登录</string>
<!-- Cloud Sync -->
<string name="sync_title">云同步</string>
<string name="sync_login_status">登录状态</string>
<string name="sync_device_id_default">设备 ID: 未初始化</string>
<string name="sync_auto_sync">自动同步</string>
<string name="sync_last_time">最后同步时间:</string>
<string name="sync_never">从不</string>
<string name="sync_status_label">同步状态:</string>
<string name="sync_status_idle">空闲</string>
<string name="sync_status_syncing">同步中...</string>
<string name="sync_status_success">同步成功</string>
<string name="sync_status_failed">同步失败</string>
<string name="sync_button_now">立即同步</string>
<string name="sync_toast_started">同步已开始</string>
<string name="sync_toast_success">同步成功完成</string>
<string name="sync_toast_failed">同步失败: %1$s</string>
<!-- Logout -->
<string name="dialog_logout_title">退出登录</string>
<string name="dialog_logout_message">确定要退出登录吗?退出后本地笔记将保留,但无法同步到云端。</string>
<string name="dialog_logout_confirm">退出</string>
<string name="toast_logout_success">已退出登录</string>
<!-- Folder -->
<string name="folder_title">文件夹</string>
<string name="error_create_folder">创建文件夹失败</string>
<!-- Conflict Resolution -->
<string name="conflict_title">笔记冲突</string>
<string name="conflict_local_version">本地版本</string>
<string name="conflict_cloud_version">云端版本</string>
<string name="conflict_use_local">使用本地</string>
<string name="conflict_use_cloud">使用云端</string>
<string name="conflict_merge">合并</string>
<string name="conflict_merge_hint">合并功能即将推出</string>
</resources>

@ -1,29 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- 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.
-->
<?xml version="1.0" encoding="utf-8"?>
<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="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="notelist_string_info">...</string>
<string name="notelist_menu_new">新建便</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>
@ -33,18 +17,24 @@
<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_web">覽網頁</string>
<string name="note_link_other">打開地圖</string>
<string name="format_move_notes_to_folder">已將所選 %1$d 便籤移到 %2$s 文件夾</string>
<!-- note list string -->
<string name="note_link_time">創建鬧鐘</string>
<string name="note_link_geo">查看地圖</string>
<!-- Text export file information -->
<string name="file_path" translatable="false">/MIUI/notes/</string>
<string name="file_name_txt_format" translatable="false">notes_%s.txt</string>
<!-- notes list string -->
<string name="format_folder_files_count" translatable="false">(%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_search"></string>
<string name="menu_delete">刪除</string>
<string name="menu_move">移動到文件夾</string>
<string name="menu_rename">重命名</string>
<string name="menu_select_title">選中了 %d 項</string>
<string name="menu_select_none">沒有選中項,操作無效</string>
<string name="menu_select_all">全選</string>
@ -60,6 +50,14 @@
<string name="menu_folder_delete">刪除文件夾</string>
<string name="menu_folder_change_name">修改文件夾名稱</string>
<string name="folder_exist">文件夾 %1$s 已存在,請重新命名</string>
<string name="menu_tasks">待辦事項</string>
<!-- Folder operation dialogs -->
<string name="dialog_rename_folder_title">重命名文件夾</string>
<string name="dialog_delete_folder_title">刪除文件夾</string>
<string name="dialog_delete_folder_with_notes">刪除 \"%1$s\" 及其 %2$d 條筆記?</string>
<string name="dialog_delete_folder_empty">刪除 \"%1$s\"?</string>
<string name="folder_name_hint">文件夾名稱</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">發送到桌面</string>
<string name="menu_alert">提醒我</string>
@ -67,20 +65,24 @@
<string name="menu_title_select_folder">選擇文件夾</string>
<string name="menu_move_parent_folder">上一級文件夾</string>
<string name="info_note_enter_desktop">已添加到桌面</string>
<string name="alert_title_delete">刪除</string>
<string name="alert_message_delete_notes">确认要刪除所選的 %d 條便籤嗎?</string>
<string name="alert_message_delete_note">确认要删除該條便籤嗎?</string>
<string name="alert_message_delete_folder">確認刪除檔夾及所包含的便簽嗎?</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>
<!-- Error information -->
<string name="error_sdcard_unmounted">SD卡被佔用不能操作</string>
<string name="error_sdcard_export">導出TXT時發生錯誤請檢查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_clock">不能空便籤設置鬧鐘提醒</string>
<string name="error_note_empty_for_send_to_desktop">不能將空便籤發送到桌面</string>
<string name="error_intent_invalid">無效的意圖</string>
<string name="error_intent_unsupported">不支持的意圖操作</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="format_exported_file_location">已將文本文件(%1$s)輸出至SD卡(%2$s)目錄</string>
<!-- Sync -->
<string name="ticker_syncing">同步便...</string>
<string name="ticker_syncing">同步便...</string>
<string name="ticker_success">同步成功</string>
<string name="ticker_fail">同步失敗</string>
<string name="ticker_cancel">同步已取消</string>
@ -88,27 +90,18 @@
<string name="error_sync_network">同步失敗,請檢查網絡和帳號設置</string>
<string name="error_sync_internal">同步失敗,發生內部錯誤</string>
<string name="error_sync_cancelled">同步已取消</string>
<string name="sync_progress_login">%1$s...</string>
<string name="sync_progress_login">%1$s...</string>
<string name="sync_progress_init_list">正在獲取服務器便籤列表...</string>
<string name="sync_progress_syncing">正在同步本地便籤...</string>
<!-- Preferences -->
<string name="preferences_title">設置</string>
<string name="preferences_account_title">同步賬號</string>
<string name="preferences_account_summary">与google task同步便簽記錄</string>
<string name="preferences_last_sync_time">上次同步于 %1$s</string>
<string name="preferences_add_account">添加賬號</string>
<string name="preferences_menu_change_account">更換賬號</string>
<string name="preferences_menu_remove_account">刪除賬號</string>
<string name="preferences_menu_cancel">取消</string>
<string name="preferences_last_sync_time_format" translatable="false">yyyy-MM-dd hh:mm:ss</string>
<string name="preferences_button_sync_immediately">立即同步</string>
<string name="preferences_button_sync_cancel">取消同步</string>
<string name="preferences_dialog_change_account_title">當前帳號 %1$s</string>
<string name="preferences_dialog_change_account_warn_msg">如更換同步帳號,過去的帳號同步信息將被清空,再次切換的同時可能會造成數據重復</string>
<string name="preferences_dialog_select_account_title">同步便簽</string>
<string name="preferences_dialog_select_account_tips">請選擇google帳號便簽將與該帳號的google task內容同步。</string>
<string name="preferences_toast_cannot_change_account">正在同步中,不能修改同步帳號</string>
<string name="preferences_toast_success_set_accout">同步帳號已設置為%1$s</string>
<string name="preferences_bg_random_appear_title">新建便籤背景顏色隨機</string>
<string name="preferences_language_title">語言</string>
<string name="folder_all">所有</string>
<string name="folder_all_templates">所有模板</string>
<string name="folder_uncategorized">未分類</string>
<string name="button_delete">刪除</string>
<string name="call_record_folder_name">通話便籤</string>
@ -121,46 +114,118 @@
<string name="datetime_dialog_ok">設置</string>
<string name="datetime_dialog_cancel">取消</string>
<plurals name="search_results_title">
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 條符合"<xliff:g id="SEARCH">%2$s</xliff:g>"的搜結果</item>
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 條符合"<xliff:g id="SEARCH">%2$s</xliff:g>"的搜結果</item>
</plurals>
<!-- Sidebar -->
<string name="root_folder_name">我的便籤</string>
<string name="folder_note_count">%d 個便籤</string>
<string name="dialog_create_folder_title">創建文件夾</string>
<string name="dialog_create_folder_hint">文件夾名稱</string>
<string name="error_folder_name_empty">文件夾名稱不能為空</string>
<string name="error_folder_name_too_long">文件夾名稱過長最多50個字符</string>
<string name="menu_trash">回收站</string>
<string name="create_folder_success">創建文件夾成功</string>
<!-- New translations for missing strings -->
<string name="menu_rename">重命名</string>
<string name="dialog_rename_folder_title">重命名文件夾</string>
<string name="dialog_delete_folder_title">刪除文件夾</string>
<string name="dialog_delete_folder_with_notes">刪除 "%1$s" 及其 %2$d 條筆記?</string>
<string name="dialog_delete_folder_empty">刪除 "%1$s"?</string>
<string name="folder_name_hint">文件夾名稱</string>
<string name="error_intent_invalid">無效的意圖</string>
<string name="error_intent_unsupported">不支持的意圖操作</string>
<string name="empty_notes_hint">暫無便籤,點擊右下角按鈕創建</string>
<string name="empty_notes_icon">空便籤圖標</string>
<string name="menu_edit_note">編輯便籤</string>
<!-- Sidebar strings -->
<string name="menu_login">登錄</string>
<string name="menu_export">導出</string>
<string name="menu_templates">模板</string>
<string name="menu_settings">設置</string>
<string name="menu_trash">回收站</string>
<string name="root_folder_name">我的便籤</string>
<string name="sidebar_close">關閉側邊欄</string>
<string name="sidebar_create_folder">創建文件夾</string>
<string name="folder_note_count">%d 個便籤</string>
<string name="dialog_create_folder_title">創建文件夾</string>
<string name="dialog_create_folder_hint">文件夾名稱</string>
<string name="error_folder_name_empty">文件夾名稱不能為空</string>
<string name="error_folder_name_too_long">文件夾名稱過長最多50個字符</string>
<string name="error_folder_name_exists">文件夾已存在</string>
<string name="create_folder_success">創建文件夾成功</string>
<string name="menu_pin">置頂</string>
<string name="menu_lock">鎖定</string>
<string name="lock_confirmation">確定需要為其上鎖?</string>
<string name="lock_confirm_button">確定</string>
<string name="lock_cancel_button">再想想</string>
<string name="delete_confirmation">確定要刪除選中的便籤嗎?</string>
<string name="menu_unpin">取消置頂</string>
<string name="menu_unlock">解鎖</string>
<!-- Search related -->
<string name="search_no_results">未找到結果</string>
<string name="search_history_title">搜索歷史</string>
<string name="search_history_clear">清空</string>
<!-- Undo/Redo -->
<string name="menu_undo">撤銷</string>
<string name="menu_redo">重做</string>
<string name="menu_clear_history">清空撤銷歷史</string>
<string name="undo_success">撤銷成功</string>
<string name="redo_success">重做成功</string>
<string name="undo_fail">無可撤銷內容</string>
<string name="redo_fail">無可重做內容</string>
<string name="menu_save_as_template">保存為模板</string>
<string name="menu_picture">圖片</string>
<string name="menu_rich_text">富文本</string>
<!-- Capsule Feature -->
<string name="accessibility_service_description">小米便籤全局速記服務。開啟後請勿開啟“無障礙快捷方式”,以免出現多餘的系統懸浮按鈕。</string>
<string name="capsule_permission_alert_window_title">需要懸浮窗權限</string>
<string name="capsule_permission_alert_window_message">全局速記膠囊需要懸浮窗權限才能顯示在其他應用上層。</string>
<string name="capsule_permission_accessibility_title">需要無障礙服務權限</string>
<string name="capsule_permission_accessibility_message">全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。</string>
<string name="preferences_capsule_title">全局速記膠囊</string>
<string name="preferences_capsule_summary">開啟側邊懸浮膠囊,支持跨應用拖拽和剪貼板速記</string>
<string name="menu_restore">恢復</string>
<string name="menu_permanent_delete">永久刪除</string>
<!-- Drawer Navigation -->
<string name="drawer_login_prompt">點擊登錄/註冊</string>
<string name="drawer_default_username">用戶</string>
<string name="drawer_default_device_id">設備 ID: 未知</string>
<string name="drawer_sync_status_synced">已同步</string>
<string name="drawer_sync_status_syncing">同步中...</string>
<string name="drawer_sync_status_failed">同步失敗</string>
<string name="drawer_group_notes">筆記</string>
<string name="drawer_group_sync">雲同步</string>
<string name="drawer_group_other">其他</string>
<string name="drawer_all_notes">全部筆記</string>
<string name="drawer_favorites">收藏</string>
<string name="drawer_reminders">提醒</string>
<string name="drawer_sync_now">立即同步</string>
<string name="drawer_sync_settings">同步設置</string>
<string name="drawer_help">幫助與反饋</string>
<string name="drawer_login">登錄</string>
<string name="drawer_logout">退出登錄</string>
<!-- Cloud Sync -->
<string name="sync_title">雲同步</string>
<string name="sync_login_status">登錄狀態</string>
<string name="sync_device_id_default">設備 ID: 未初始化</string>
<string name="sync_auto_sync">自動同步</string>
<string name="sync_last_time">最後同步時間:</string>
<string name="sync_never">從不</string>
<string name="sync_status_label">同步狀態:</string>
<string name="sync_status_idle">空閒</string>
<string name="sync_status_syncing">同步中...</string>
<string name="sync_status_success">同步成功</string>
<string name="sync_status_failed">同步失敗</string>
<string name="sync_button_now">立即同步</string>
<string name="sync_toast_started">同步已開始</string>
<string name="sync_toast_success">同步成功完成</string>
<string name="sync_toast_failed">同步失敗: %1$s</string>
<!-- Logout -->
<string name="dialog_logout_title">退出登錄</string>
<string name="dialog_logout_message">確定要退出登錄嗎?退出後本地筆記將保留,但無法同步到雲端。</string>
<string name="dialog_logout_confirm">退出</string>
<string name="toast_logout_success">已退出登錄</string>
<!-- Folder -->
<string name="folder_title">文件夾</string>
<string name="error_create_folder">創建文件夾失敗</string>
<!-- Conflict Resolution -->
<string name="conflict_title">筆記衝突</string>
<string name="conflict_local_version">本地版本</string>
<string name="conflict_cloud_version">雲端版本</string>
<string name="conflict_use_local">使用本地</string>
<string name="conflict_use_cloud">使用雲端</string>
<string name="conflict_merge">合併</string>
<string name="conflict_merge_hint">合併功能即將推出</string>
</resources>

@ -40,4 +40,32 @@
<item>light</item>
<item>dark</item>
</string-array>
<string-array name="font_size_entries">
<item>@string/menu_font_small</item>
<item>@string/menu_font_normal</item>
<item>@string/menu_font_large</item>
<item>@string/menu_font_super</item>
</string-array>
<string-array name="font_size_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="language_entries">
<item>简体中文</item>
<item>繁體中文</item>
<item>English</item>
<item>System Default</item>
</string-array>
<string-array name="language_values">
<item>zh-CN</item>
<item>zh-TW</item>
<item>en</item>
<item>system</item>
</string-array>
</resources>

@ -108,26 +108,16 @@
<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" translatable="false">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_language_title">Language</string>
<string name="folder_all">All</string>
<string name="folder_all_templates">All Templates</string>
<string name="folder_uncategorized">Uncategorized</string>
<string name="button_delete">Delete</string>
<string name="call_record_folder_name">Call notes</string>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<cache-path name="cache_files" path="." />
</paths>

@ -6,6 +6,25 @@
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
android:defaultValue="system" />
<ListPreference
android:key="pref_font_size"
android:title="@string/menu_font_size"
android:entries="@array/font_size_entries"
android:entryValues="@array/font_size_values"
android:defaultValue="1" />
<SwitchPreferenceCompat
android:key="pref_key_bg_random_appear"
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
<ListPreference
android:key="pref_language"
android:title="@string/preferences_language_title"
android:entries="@array/language_entries"
android:entryValues="@array/language_values"
android:defaultValue="zh-CN" />
</PreferenceCategory>
<PreferenceCategory android:title="安全">
@ -23,14 +42,21 @@
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="云同步"
android:key="pref_sync_account_key">
<!-- 账户信息将由代码动态插入 -->
<PreferenceCategory android:title="数据管理">
<Preference
android:key="pref_key_cloud_sync"
android:title="@string/sync_title"
android:summary="@string/sync_auto_sync" />
<Preference
android:key="pref_key_export_notes"
android:title="@string/menu_export_text"
android:summary="将笔记导出为文本文件保存到存储卡" />
</PreferenceCategory>
<PreferenceCategory android:title="关于">
<Preference
android:title="版本"
android:summary="1.0.0" />
android:summary="1.1.0" />
</PreferenceCategory>
</PreferenceScreen>

Loading…
Cancel
Save