|
|
|
|
@ -27,7 +27,6 @@ import android.content.Context;
|
|
|
|
|
import android.content.DialogInterface;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
|
import android.graphics.Paint;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.preference.PreferenceManager;
|
|
|
|
|
@ -47,9 +46,6 @@ import android.view.View.OnClickListener;
|
|
|
|
|
import android.view.WindowManager;
|
|
|
|
|
import android.widget.Button;
|
|
|
|
|
import android.widget.CheckBox;
|
|
|
|
|
import android.widget.LinearLayout;
|
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
import android.util.TypedValue;
|
|
|
|
|
import android.widget.CompoundButton;
|
|
|
|
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
|
|
|
import android.widget.EditText;
|
|
|
|
|
@ -57,28 +53,11 @@ import android.widget.ImageButton;
|
|
|
|
|
import android.widget.ImageView;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
|
import androidx.core.app.ActivityCompat;
|
|
|
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
|
import android.Manifest;
|
|
|
|
|
import net.micode.notes.data.Notes;
|
|
|
|
|
import net.micode.notes.data.Notes.NoteColumns;
|
|
|
|
|
import android.widget.LinearLayout;
|
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.provider.MediaStore;
|
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
|
import android.graphics.BitmapFactory;
|
|
|
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
|
import android.text.Html;
|
|
|
|
|
import android.util.Base64;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import android.content.pm.ResolveInfo;
|
|
|
|
|
|
|
|
|
|
import net.micode.notes.R;
|
|
|
|
|
import net.micode.notes.data.Notes;
|
|
|
|
|
@ -193,9 +172,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
/** 笔记编辑面板 */
|
|
|
|
|
private View mNoteEditorPanel;
|
|
|
|
|
|
|
|
|
|
/** 字符统计显示文本框 */
|
|
|
|
|
private TextView mTvCharCount;
|
|
|
|
|
|
|
|
|
|
/** 工作笔记对象,用于处理笔记数据 */
|
|
|
|
|
private WorkingNote mWorkingNote;
|
|
|
|
|
|
|
|
|
|
@ -217,13 +193,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
|
|
|
|
|
/** 模板选择请求码 */
|
|
|
|
|
private static final int REQUEST_CODE_TEMPLATE = 1001;
|
|
|
|
|
|
|
|
|
|
/** 图片选择请求码 */
|
|
|
|
|
private static final int REQUEST_CODE_IMAGE_PICK = 1002;
|
|
|
|
|
/** 拍照请求码 */
|
|
|
|
|
private static final int REQUEST_CODE_CAMERA = 1003;
|
|
|
|
|
/** 相机权限请求码 */
|
|
|
|
|
private static final int REQUEST_CAMERA_PERMISSION = 1004;
|
|
|
|
|
|
|
|
|
|
/** 清单模式下的编辑文本列表 */
|
|
|
|
|
private LinearLayout mEditTextList;
|
|
|
|
|
@ -377,37 +346,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
} else {
|
|
|
|
|
// 解析HTML格式的富文本内容,添加null检查防止闪退
|
|
|
|
|
String content = mWorkingNote.getContent();
|
|
|
|
|
CharSequence htmlContent = Html.fromHtml(content == null ? "" : content, new Html.ImageGetter() {
|
|
|
|
|
@Override
|
|
|
|
|
public Drawable getDrawable(String source) {
|
|
|
|
|
// 处理data:image格式的图片
|
|
|
|
|
if (source.startsWith("data:image/")) {
|
|
|
|
|
try {
|
|
|
|
|
// 提取Base64部分
|
|
|
|
|
String base64 = source.substring(source.indexOf(",") + 1);
|
|
|
|
|
// 解码Base64
|
|
|
|
|
byte[] data = Base64.decode(base64, Base64.DEFAULT);
|
|
|
|
|
// 创建Bitmap
|
|
|
|
|
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
|
|
|
|
|
if (bitmap != null) {
|
|
|
|
|
// 创建Drawable
|
|
|
|
|
BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
|
|
|
|
|
// 设置Drawable的边界
|
|
|
|
|
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
|
|
|
|
return drawable;
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error loading image: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}, null);
|
|
|
|
|
CharSequence htmlContent = Html.fromHtml(content == null ? "" : content);
|
|
|
|
|
mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery));
|
|
|
|
|
mNoteEditor.setSelection(mNoteEditor.getText().length());
|
|
|
|
|
}
|
|
|
|
|
// 初始化字符统计显示
|
|
|
|
|
updateCharCount();
|
|
|
|
|
for (Integer id : sBgSelectorSelectionMap.keySet()) {
|
|
|
|
|
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
@ -538,9 +480,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
mNoteHeaderHolder.ibSetBgColor = (ImageButton) findViewById(R.id.btn_set_bg_color);
|
|
|
|
|
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
|
|
|
|
|
mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view);
|
|
|
|
|
mNoteEditor.setOnTextViewChangeListener(this);
|
|
|
|
|
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
|
|
|
|
|
mTvCharCount = (TextView) findViewById(R.id.tv_char_count);
|
|
|
|
|
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
|
|
|
|
|
for (int id : sBgSelectorBtnsMap.keySet()) {
|
|
|
|
|
ImageView iv = (ImageView) findViewById(id);
|
|
|
|
|
@ -828,15 +768,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
case R.id.menu_unlock:
|
|
|
|
|
showPasswordDialogForUnlock();
|
|
|
|
|
break;
|
|
|
|
|
case R.id.menu_change_password:
|
|
|
|
|
showChangePasswordDialog();
|
|
|
|
|
break;
|
|
|
|
|
case R.id.menu_note_template:
|
|
|
|
|
openTemplateSelector();
|
|
|
|
|
break;
|
|
|
|
|
case R.id.menu_insert_image:
|
|
|
|
|
showImageSourceDialog();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
@ -878,41 +812,23 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
/**
|
|
|
|
|
* 显示密码输入对话框用于锁定笔记
|
|
|
|
|
* <p>
|
|
|
|
|
* 该方法用于显示一个密码输入对话框,让用户输入密码和密码提示来锁定当前编辑的笔记
|
|
|
|
|
* 该方法用于显示一个密码输入对话框,让用户输入密码来锁定当前编辑的笔记
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
private void showPasswordDialogForLock() {
|
|
|
|
|
// 创建布局并添加密码输入框
|
|
|
|
|
LinearLayout layout = new LinearLayout(this);
|
|
|
|
|
layout.setOrientation(LinearLayout.VERTICAL);
|
|
|
|
|
layout.setPadding(60, 40, 60, 40);
|
|
|
|
|
layout.setPadding(60, 40, 60, 40);
|
|
|
|
|
// 设置子视图间距
|
|
|
|
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
|
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
|
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
|
|
|
params.setMargins(0, 0, 0, 20); // 底部外边距作为间距
|
|
|
|
|
|
|
|
|
|
final EditText passwordEditText = new EditText(this);
|
|
|
|
|
passwordEditText.setHint(R.string.hint_enter_password);
|
|
|
|
|
passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
|
|
|
layout.addView(passwordEditText);
|
|
|
|
|
|
|
|
|
|
// 添加密码提示输入框
|
|
|
|
|
final EditText hintEditText = new EditText(this);
|
|
|
|
|
hintEditText.setHint(R.string.hint_enter_password_hint);
|
|
|
|
|
layout.addView(hintEditText);
|
|
|
|
|
|
|
|
|
|
new AlertDialog.Builder(this)
|
|
|
|
|
.setTitle(R.string.dialog_enter_password)
|
|
|
|
|
.setView(layout)
|
|
|
|
|
.setView(passwordEditText)
|
|
|
|
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
String password = passwordEditText.getText().toString();
|
|
|
|
|
String hint = hintEditText.getText().toString();
|
|
|
|
|
if (!TextUtils.isEmpty(password)) {
|
|
|
|
|
lockCurrentNote(password, hint);
|
|
|
|
|
lockCurrentNote(password);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
@ -927,50 +843,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
private void showPasswordDialogForUnlock() {
|
|
|
|
|
// 查询当前笔记的密码提示
|
|
|
|
|
String[] projection = {NoteColumns.LOCK_HINT};
|
|
|
|
|
String selection = NoteColumns.ID + "=?";
|
|
|
|
|
String[] selectionArgs = {String.valueOf(mWorkingNote.getNoteId())};
|
|
|
|
|
|
|
|
|
|
Cursor cursor = getContentResolver().query(Notes.CONTENT_NOTE_URI, projection, selection, selectionArgs, null);
|
|
|
|
|
String lockHint = null;
|
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
|
lockHint = cursor.getString(0);
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建布局并添加密码输入框
|
|
|
|
|
LinearLayout layout = new LinearLayout(this);
|
|
|
|
|
layout.setOrientation(LinearLayout.VERTICAL);
|
|
|
|
|
layout.setPadding(60, 40, 60, 40);
|
|
|
|
|
|
|
|
|
|
// 创建布局参数,用于设置子视图间距
|
|
|
|
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
|
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
|
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
|
|
|
params.setMargins(0, 0, 0, 20); // 底部外边距作为间距
|
|
|
|
|
|
|
|
|
|
final EditText passwordEditText = new EditText(this);
|
|
|
|
|
passwordEditText.setHint(R.string.hint_enter_password);
|
|
|
|
|
passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
|
|
|
layout.addView(passwordEditText, params);
|
|
|
|
|
|
|
|
|
|
// 如果有密码提示,显示提示信息
|
|
|
|
|
if (!TextUtils.isEmpty(lockHint)) {
|
|
|
|
|
TextView hintTextView = new TextView(this);
|
|
|
|
|
hintTextView.setText(getString(R.string.password_hint_prefix) + lockHint);
|
|
|
|
|
hintTextView.setTextColor(getResources().getColor(R.color.secondary_text_dark));
|
|
|
|
|
hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
|
|
|
|
// 提示文本不需要底部间距
|
|
|
|
|
LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
|
|
|
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
|
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
|
|
|
layout.addView(hintTextView, hintParams);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new AlertDialog.Builder(this)
|
|
|
|
|
.setTitle(R.string.dialog_enter_password)
|
|
|
|
|
.setView(layout)
|
|
|
|
|
.setView(passwordEditText)
|
|
|
|
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
@ -987,20 +866,17 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
/**
|
|
|
|
|
* 锁定当前笔记
|
|
|
|
|
* <p>
|
|
|
|
|
* 该方法用于锁定当前编辑的笔记,更新其锁定状态、密码、密码提示和锁定类型
|
|
|
|
|
* 该方法用于锁定当前编辑的笔记,更新其锁定状态、密码和锁定类型
|
|
|
|
|
* </p>
|
|
|
|
|
* @param password 用于锁定的密码
|
|
|
|
|
* @param hint 用于帮助记忆密码的提示
|
|
|
|
|
*/
|
|
|
|
|
private void lockCurrentNote(String password, String hint) {
|
|
|
|
|
private void lockCurrentNote(String password) {
|
|
|
|
|
// 设置锁定状态为1(已锁定)
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.IS_LOCKED, "1");
|
|
|
|
|
// 设置加密后的密码
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.LOCK_PASSWORD, encryptPassword(password));
|
|
|
|
|
// 设置锁定类型为笔记类型
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.LOCK_TYPE, String.valueOf(Notes.LOCK_TYPE_NOTE));
|
|
|
|
|
// 设置密码提示
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.LOCK_HINT, hint);
|
|
|
|
|
// 保存笔记
|
|
|
|
|
saveNote();
|
|
|
|
|
// 显示提示信息
|
|
|
|
|
@ -1065,97 +941,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
return password; // 加密失败时返回原始密码
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 显示修改密码对话框
|
|
|
|
|
* <p>
|
|
|
|
|
* 该方法用于显示一个修改密码对话框,让用户输入旧密码、新密码和新的密码提示
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
private void showChangePasswordDialog() {
|
|
|
|
|
// 创建布局并添加输入框
|
|
|
|
|
LinearLayout layout = new LinearLayout(this);
|
|
|
|
|
layout.setOrientation(LinearLayout.VERTICAL);
|
|
|
|
|
layout.setPadding(60, 40, 60, 40);
|
|
|
|
|
|
|
|
|
|
// 创建布局参数,用于设置子视图间距
|
|
|
|
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
|
|
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
|
|
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
|
|
|
params.setMargins(0, 0, 0, 20); // 底部外边距作为间距
|
|
|
|
|
|
|
|
|
|
// 旧密码输入框
|
|
|
|
|
final EditText oldPasswordEditText = new EditText(this);
|
|
|
|
|
oldPasswordEditText.setHint(R.string.hint_enter_password);
|
|
|
|
|
oldPasswordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
|
|
|
layout.addView(oldPasswordEditText, params);
|
|
|
|
|
|
|
|
|
|
// 新密码输入框
|
|
|
|
|
final EditText newPasswordEditText = new EditText(this);
|
|
|
|
|
newPasswordEditText.setHint(R.string.hint_enter_password);
|
|
|
|
|
newPasswordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
|
|
|
layout.addView(newPasswordEditText, params);
|
|
|
|
|
|
|
|
|
|
// 新密码提示输入框
|
|
|
|
|
final EditText newHintEditText = new EditText(this);
|
|
|
|
|
newHintEditText.setHint(R.string.hint_enter_password_hint);
|
|
|
|
|
layout.addView(newHintEditText);
|
|
|
|
|
|
|
|
|
|
new AlertDialog.Builder(this)
|
|
|
|
|
.setTitle(R.string.menu_change_password)
|
|
|
|
|
.setView(layout)
|
|
|
|
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
String oldPassword = oldPasswordEditText.getText().toString();
|
|
|
|
|
String newPassword = newPasswordEditText.getText().toString();
|
|
|
|
|
String newHint = newHintEditText.getText().toString();
|
|
|
|
|
if (!TextUtils.isEmpty(oldPassword) && !TextUtils.isEmpty(newPassword)) {
|
|
|
|
|
changePassword(oldPassword, newPassword, newHint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修改密码
|
|
|
|
|
* <p>
|
|
|
|
|
* 该方法用于修改当前笔记的密码,需要验证旧密码,验证通过后更新新密码和密码提示
|
|
|
|
|
* </p>
|
|
|
|
|
* @param oldPassword 旧密码
|
|
|
|
|
* @param newPassword 新密码
|
|
|
|
|
* @param newHint 新的密码提示
|
|
|
|
|
*/
|
|
|
|
|
private void changePassword(String oldPassword, String newPassword, String newHint) {
|
|
|
|
|
// 使用ContentResolver直接查询数据库获取加密密码
|
|
|
|
|
String[] projection = {NoteColumns.LOCK_PASSWORD};
|
|
|
|
|
String selection = NoteColumns.ID + "=?";
|
|
|
|
|
String[] selectionArgs = {String.valueOf(mWorkingNote.getNoteId())};
|
|
|
|
|
|
|
|
|
|
Cursor cursor = getContentResolver().query(Notes.CONTENT_NOTE_URI, projection, selection, selectionArgs, null);
|
|
|
|
|
String encryptedPassword = null;
|
|
|
|
|
|
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
|
encryptedPassword = cursor.getString(0);
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证旧密码是否正确
|
|
|
|
|
if (encryptedPassword != null && encryptedPassword.equals(encryptPassword(oldPassword))) {
|
|
|
|
|
// 旧密码正确,更新新密码和密码提示
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.LOCK_PASSWORD, encryptPassword(newPassword));
|
|
|
|
|
mWorkingNote.setNoteValue(NoteColumns.LOCK_HINT, newHint);
|
|
|
|
|
// 保存笔记
|
|
|
|
|
saveNote();
|
|
|
|
|
// 显示提示信息
|
|
|
|
|
Toast.makeText(this, getString(R.string.message_password_changed), Toast.LENGTH_SHORT).show();
|
|
|
|
|
} else {
|
|
|
|
|
// 旧密码错误,显示错误信息
|
|
|
|
|
Toast.makeText(this, getString(R.string.error_wrong_password), Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建新笔记
|
|
|
|
|
@ -1444,15 +1229,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
} else {
|
|
|
|
|
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
// 更新字符统计
|
|
|
|
|
updateCharCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当文本内容变化时,更新字符统计
|
|
|
|
|
*/
|
|
|
|
|
public void onContentChange() {
|
|
|
|
|
updateCharCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -1606,45 +1382,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
showToast(resId, Toast.LENGTH_SHORT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算字符串中的有效字符数(不包括空格、换行符等空白字符)
|
|
|
|
|
* @param text 要计算的字符串
|
|
|
|
|
* @return 有效字符数
|
|
|
|
|
*/
|
|
|
|
|
private int countValidCharacters(String text) {
|
|
|
|
|
if (TextUtils.isEmpty(text)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
// 去除所有空白字符后计算长度
|
|
|
|
|
return text.replaceAll("\\s", "").length();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新字符统计显示
|
|
|
|
|
*/
|
|
|
|
|
private void updateCharCount() {
|
|
|
|
|
int charCount = 0;
|
|
|
|
|
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
|
|
|
|
|
// 列表模式:遍历所有列表项
|
|
|
|
|
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
|
|
|
|
|
View view = mEditTextList.getChildAt(i);
|
|
|
|
|
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
|
|
|
|
|
if (!TextUtils.isEmpty(edit.getText())) {
|
|
|
|
|
charCount += countValidCharacters(edit.getText().toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 普通文本模式:直接计算编辑框内容
|
|
|
|
|
if (!TextUtils.isEmpty(mNoteEditor.getText())) {
|
|
|
|
|
charCount = countValidCharacters(mNoteEditor.getText().toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 更新显示
|
|
|
|
|
if (mTvCharCount != null) {
|
|
|
|
|
mTvCharCount.setText(charCount + " 字符");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 打开模板选择页面
|
|
|
|
|
* <p>
|
|
|
|
|
@ -1662,271 +1399,37 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理活动结果
|
|
|
|
|
* 处理从模板选择页面返回的结果
|
|
|
|
|
* <p>
|
|
|
|
|
* 该方法在从TemplateSelectActivity返回时被调用,
|
|
|
|
|
* 如果用户选择了模板,则更新当前笔记的内容
|
|
|
|
|
* </p>
|
|
|
|
|
* @param requestCode 请求码
|
|
|
|
|
* @param resultCode 结果码
|
|
|
|
|
* @param data 返回的Intent数据
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
|
if (resultCode == RESULT_OK) {
|
|
|
|
|
switch (requestCode) {
|
|
|
|
|
case REQUEST_CODE_TEMPLATE:
|
|
|
|
|
// 模板选择结果处理
|
|
|
|
|
if (data != null) {
|
|
|
|
|
String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT);
|
|
|
|
|
if (!TextUtils.isEmpty(templateContent)) {
|
|
|
|
|
// 保存模板内容到WorkingNote,防止onResume时被覆盖
|
|
|
|
|
mWorkingNote.setWorkingText(templateContent);
|
|
|
|
|
|
|
|
|
|
// 更新笔记内容
|
|
|
|
|
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
|
|
|
|
|
switchToListMode(templateContent);
|
|
|
|
|
} else {
|
|
|
|
|
mNoteEditor.setText(Html.fromHtml(templateContent));
|
|
|
|
|
mNoteEditor.setSelection(mNoteEditor.getText().length());
|
|
|
|
|
}
|
|
|
|
|
Toast.makeText(this, R.string.notealert_enter, Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case REQUEST_CODE_IMAGE_PICK:
|
|
|
|
|
// 从相册选择图片
|
|
|
|
|
if (data != null && data.getData() != null) {
|
|
|
|
|
handleImageSelection(data.getData());
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case REQUEST_CODE_CAMERA:
|
|
|
|
|
// 拍照结果
|
|
|
|
|
if (resultCode == RESULT_OK) {
|
|
|
|
|
if (data != null && data.getExtras() != null) {
|
|
|
|
|
Bitmap bitmap = (Bitmap) data.getExtras().get("data");
|
|
|
|
|
if (bitmap != null) {
|
|
|
|
|
insertImageToNote(bitmap);
|
|
|
|
|
} else {
|
|
|
|
|
showToast(R.string.error_image_capture);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
showToast(R.string.error_image_capture);
|
|
|
|
|
}
|
|
|
|
|
} else if (resultCode == RESULT_CANCELED) {
|
|
|
|
|
// 用户取消拍照,不显示错误
|
|
|
|
|
if (requestCode == REQUEST_CODE_TEMPLATE && resultCode == RESULT_OK) {
|
|
|
|
|
if (data != null) {
|
|
|
|
|
String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT);
|
|
|
|
|
if (!TextUtils.isEmpty(templateContent)) {
|
|
|
|
|
// 保存模板内容到WorkingNote,防止onResume时被覆盖
|
|
|
|
|
mWorkingNote.setWorkingText(templateContent);
|
|
|
|
|
|
|
|
|
|
// 更新笔记内容
|
|
|
|
|
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
|
|
|
|
|
switchToListMode(templateContent);
|
|
|
|
|
} else {
|
|
|
|
|
showToast(R.string.error_image_capture);
|
|
|
|
|
mNoteEditor.setText(Html.fromHtml(templateContent));
|
|
|
|
|
mNoteEditor.setSelection(mNoteEditor.getText().length());
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 显示图片来源选择对话框
|
|
|
|
|
*/
|
|
|
|
|
private void showImageSourceDialog() {
|
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
|
|
|
builder.setTitle(R.string.menu_insert_image);
|
|
|
|
|
builder.setItems(new CharSequence[] {
|
|
|
|
|
getString(R.string.menu_take_photo),
|
|
|
|
|
getString(R.string.menu_choose_from_gallery)
|
|
|
|
|
}, new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
switch (which) {
|
|
|
|
|
case 0:
|
|
|
|
|
// 拍照
|
|
|
|
|
if (ContextCompat.checkSelfPermission(NoteEditActivity.this, Manifest.permission.CAMERA)
|
|
|
|
|
!= PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
// 请求相机权限
|
|
|
|
|
ActivityCompat.requestPermissions(NoteEditActivity.this,
|
|
|
|
|
new String[]{Manifest.permission.CAMERA},
|
|
|
|
|
REQUEST_CAMERA_PERMISSION);
|
|
|
|
|
} else {
|
|
|
|
|
// 权限已授予,启动相机
|
|
|
|
|
launchCamera();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
// 从相册选择
|
|
|
|
|
Intent galleryIntent = new Intent(Intent.ACTION_PICK,
|
|
|
|
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
|
|
|
|
galleryIntent.setType("image/*");
|
|
|
|
|
startActivityForResult(galleryIntent, REQUEST_CODE_IMAGE_PICK);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
builder.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 启动相机
|
|
|
|
|
*/
|
|
|
|
|
private void launchCamera() {
|
|
|
|
|
Log.d(TAG, "Attempting to launch camera");
|
|
|
|
|
|
|
|
|
|
// 检查相机权限状态
|
|
|
|
|
int permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
|
|
|
|
|
Log.d(TAG, "Camera permission status: " + permissionStatus);
|
|
|
|
|
|
|
|
|
|
// 尝试使用不同的相机 Intent 构造方式
|
|
|
|
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
|
|
|
|
|
|
|
|
// 检查是否有应用可以处理相机 Intent
|
|
|
|
|
PackageManager packageManager = getPackageManager();
|
|
|
|
|
if (packageManager != null) {
|
|
|
|
|
Log.d(TAG, "PackageManager obtained successfully");
|
|
|
|
|
|
|
|
|
|
// 方法1:使用 resolveActivity
|
|
|
|
|
if (cameraIntent.resolveActivity(packageManager) != null) {
|
|
|
|
|
Log.d(TAG, "Camera intent resolved successfully using resolveActivity");
|
|
|
|
|
try {
|
|
|
|
|
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
|
|
|
|
|
return;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error starting camera: " + e.getMessage());
|
|
|
|
|
Toast.makeText(this, R.string.notealert_enter, Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.d(TAG, "Camera intent not resolved using resolveActivity");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 方法2:使用 queryIntentActivities
|
|
|
|
|
List<ResolveInfo> activities = packageManager.queryIntentActivities(cameraIntent, 0);
|
|
|
|
|
if (activities != null && !activities.isEmpty()) {
|
|
|
|
|
Log.d(TAG, "Camera intent resolved successfully using queryIntentActivities, found " + activities.size() + " activities");
|
|
|
|
|
try {
|
|
|
|
|
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
|
|
|
|
|
return;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error starting camera: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.d(TAG, "Camera intent not resolved using queryIntentActivities");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Failed to obtain PackageManager");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尝试使用更具体的相机 Intent
|
|
|
|
|
Intent explicitCameraIntent = new Intent();
|
|
|
|
|
explicitCameraIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
|
|
|
explicitCameraIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
|
|
|
try {
|
|
|
|
|
Log.d(TAG, "Attempting to start camera with explicit intent");
|
|
|
|
|
startActivityForResult(explicitCameraIntent, REQUEST_CODE_CAMERA);
|
|
|
|
|
return;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error starting camera with explicit intent: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 所有方法都失败,显示错误提示
|
|
|
|
|
Log.e(TAG, "All attempts to launch camera failed");
|
|
|
|
|
showToast(R.string.error_image_selection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理从相册选择的图片
|
|
|
|
|
*/
|
|
|
|
|
private void handleImageSelection(Uri uri) {
|
|
|
|
|
try {
|
|
|
|
|
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
|
|
|
|
|
if (bitmap != null) {
|
|
|
|
|
insertImageToNote(bitmap);
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Log.e(TAG, "Error loading image: " + e.getMessage());
|
|
|
|
|
showToast(R.string.error_image_selection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将图片插入到笔记中
|
|
|
|
|
*/
|
|
|
|
|
private void insertImageToNote(Bitmap bitmap) {
|
|
|
|
|
try {
|
|
|
|
|
// 压缩图片
|
|
|
|
|
Bitmap compressedBitmap = compressBitmap(bitmap, 800, 600, 80);
|
|
|
|
|
// 转换为Base64
|
|
|
|
|
String base64Image = bitmapToBase64(compressedBitmap);
|
|
|
|
|
// 创建HTML图片标签
|
|
|
|
|
String imageTag = "<img src=\"data:image/jpeg;base64," + base64Image + "\" style=\"max-width:100%;height:auto;\" />";
|
|
|
|
|
|
|
|
|
|
// 获取当前光标位置
|
|
|
|
|
int cursorPosition = mNoteEditor.getSelectionStart();
|
|
|
|
|
// 获取当前文本
|
|
|
|
|
String currentText = mNoteEditor.getText().toString();
|
|
|
|
|
// 插入图片标签
|
|
|
|
|
String newText = currentText.substring(0, cursorPosition) + imageTag + currentText.substring(cursorPosition);
|
|
|
|
|
|
|
|
|
|
// 更新编辑器内容
|
|
|
|
|
mNoteEditor.setText(Html.fromHtml(newText, new Html.ImageGetter() {
|
|
|
|
|
@Override
|
|
|
|
|
public Drawable getDrawable(String source) {
|
|
|
|
|
// 处理data:image格式的图片
|
|
|
|
|
if (source.startsWith("data:image/")) {
|
|
|
|
|
try {
|
|
|
|
|
// 提取Base64部分
|
|
|
|
|
String base64 = source.substring(source.indexOf(",") + 1);
|
|
|
|
|
// 解码Base64
|
|
|
|
|
byte[] data = Base64.decode(base64, Base64.DEFAULT);
|
|
|
|
|
// 创建Bitmap
|
|
|
|
|
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
|
|
|
|
|
if (bitmap != null) {
|
|
|
|
|
// 创建Drawable
|
|
|
|
|
BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
|
|
|
|
|
// 设置Drawable的边界
|
|
|
|
|
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
|
|
|
|
return drawable;
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error loading image from data URL: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}, null));
|
|
|
|
|
// 设置光标位置到图片后面
|
|
|
|
|
mNoteEditor.setSelection(mNoteEditor.getText().length());
|
|
|
|
|
|
|
|
|
|
// 保存笔记
|
|
|
|
|
saveNote();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Error inserting image: " + e.getMessage());
|
|
|
|
|
showToast(R.string.error_image_insertion);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 压缩图片
|
|
|
|
|
*/
|
|
|
|
|
private Bitmap compressBitmap(Bitmap bitmap, int maxWidth, int maxHeight, int quality) {
|
|
|
|
|
int width = bitmap.getWidth();
|
|
|
|
|
int height = bitmap.getHeight();
|
|
|
|
|
|
|
|
|
|
// 计算缩放比例
|
|
|
|
|
float scaleWidth = ((float) maxWidth) / width;
|
|
|
|
|
float scaleHeight = ((float) maxHeight) / height;
|
|
|
|
|
float scale = Math.min(scaleWidth, scaleHeight);
|
|
|
|
|
|
|
|
|
|
// 如果不需要缩放,直接返回原图片
|
|
|
|
|
if (scale >= 1) {
|
|
|
|
|
return bitmap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算新尺寸
|
|
|
|
|
int newWidth = Math.round(width * scale);
|
|
|
|
|
int newHeight = Math.round(height * scale);
|
|
|
|
|
|
|
|
|
|
// 创建压缩后的图片
|
|
|
|
|
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将Bitmap转换为Base64字符串
|
|
|
|
|
*/
|
|
|
|
|
private String bitmapToBase64(Bitmap bitmap) {
|
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
|
|
|
|
byte[] byteArray = baos.toByteArray();
|
|
|
|
|
return Base64.encodeToString(byteArray, Base64.DEFAULT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 显示Toast提示信息
|
|
|
|
|
@ -1939,21 +1442,4 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
private void showToast(int resId, int duration) {
|
|
|
|
|
Toast.makeText(this, resId, duration).show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理权限请求结果
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
|
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
|
|
|
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
|
|
|
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
// 权限授予成功,启动相机
|
|
|
|
|
launchCamera();
|
|
|
|
|
} else {
|
|
|
|
|
// 权限被拒绝,显示错误提示
|
|
|
|
|
showToast(R.string.error_camera_permission);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|