diff --git a/.gitignore b/.gitignore index 04fe8e7..2a08bc8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.idea/ /app/build/ /gradle/wrapper/gradle-wrapper.jar +/local.properties diff --git a/app/build.gradle b/app/build.gradle index a51d330..7438e65 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,9 +6,9 @@ android { defaultConfig { applicationId "net.micode.notes" - minSdkVersion 19 + minSdkVersion 33 //noinspection ExpiredTargetSdkVersion - targetSdkVersion 30 + targetSdkVersion 33 } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 77d2c67..40fe148 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,6 @@ - @@ -33,10 +32,27 @@ + + + + + + + + + + + + + + android:label="@string/app_name" + android:requestLegacyExternalStorage="true" + > 0 ? true : false); + return mAlertDate > 0; } public String getContent() { @@ -394,4 +390,5 @@ public class WorkingNote { */ void onCheckListModeChanged(int oldMode, int newMode); } + } diff --git a/app/src/main/java/net/micode/notes/tool/DataUtils.java b/app/src/main/java/net/micode/notes/tool/DataUtils.java index 75047aa..81b6bcc 100644 --- a/app/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/app/src/main/java/net/micode/notes/tool/DataUtils.java @@ -64,9 +64,7 @@ public class DataUtils { return false; } return true; - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } catch (OperationApplicationException e) { + } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); } return false; diff --git a/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..a2f0826 100644 --- a/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -64,7 +64,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD Intent intent = getIntent(); try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + mNoteId = Long.parseLong(intent.getData().getPathSegments().get(1)); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) diff --git a/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 0237f9d..b7c69bc 100644 --- a/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -16,25 +16,36 @@ package net.micode.notes.ui; -import android.app.Activity; +import android.Manifest; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Paint; +import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.preference.PreferenceManager; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -47,11 +58,16 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; import net.micode.notes.R; @@ -67,6 +83,7 @@ import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; +import java.io.FileNotFoundException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -154,6 +171,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe private String mUserQuery; private Pattern mPattern; + private ImageButton mImageButton; // 添加图片按钮 + + private ActivityResultLauncher requestPermissionLauncher; //获取存储权限 @Override protected void onCreate(Bundle savedInstanceState) { @@ -300,8 +320,12 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe * is not ready */ showAlertHeader(); + //将有图片路径的位置转换为图片 + requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES); } + + private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); @@ -316,7 +340,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe } else { mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; + } if(mWorkingNote.getIsLocked()){ mNoteHeaderHolder.ivUnlocked.setVisibility(View.GONE); mNoteHeaderHolder.ivLocked.setVisibility(View.VISIBLE); @@ -369,13 +393,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; - } - return true; + return !(ev.getX() < x) + && !(ev.getX() > (x + view.getWidth())) + && !(ev.getY() < y) + && !(ev.getY() > (y + view.getHeight())); } private void initResources() { @@ -402,7 +423,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); - }; + } mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); /** @@ -414,6 +435,13 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + + // 获取读取存储权限 + requestPermissionLauncher=getrequestDataLauncher(); + // 初始化添加图片按钮 + mImageButton = (ImageButton) findViewById(R.id.add_img_btn); + ActivityResultLauncher intentActivityResultLauncher=getActivityResultLauncher(); + mImageButton.setOnClickListener(view -> onAddImage(intentActivityResultLauncher)); } @Override @@ -931,5 +959,233 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListe Toast.makeText(this, resId, duration).show(); } + /** + * @author: ClearDewy + * @date: 2023/3/9 11:20 + * @param: [android.content.Context, android.net.Uri] + * @return: java.lang.String + * @description:根据Url解析文件路径 + **/ + public static String getPathFromUri(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + // 判斷是否為Android 4.4之後的版本 + if (DocumentsContract.isDocumentUri(context, uri)) { + // 如果是Android 4.4之後的版本,而且屬於文件URI + final String authority = uri.getAuthority(); + // 判斷Authority是否為本地端檔案所使用的 + if ("com.android.externalstorage.documents".equals(authority)) { + // 外部儲存空間 + final String docId = DocumentsContract.getDocumentId(uri); + final String[] divide = docId.split(":"); + final String type = divide[0]; + if ("primary".equals(type)) { + String path = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/").concat(divide[1]); + return path; + } else { + String path = "/storage/".concat(type).concat("/").concat(divide[1]); + return path; + } + } else if ("com.android.providers.downloads.documents".equals(authority)) { + // 下載目錄 + final String docId = DocumentsContract.getDocumentId(uri); + if (docId.startsWith("raw:")) { + final String path = docId.replaceFirst("raw:", ""); + return path; + } + final Uri downloadUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(docId)); + String path = queryAbsolutePath(context, downloadUri); + return path; + } else if ("com.android.providers.media.documents".equals(authority)) { + // 圖片、影音檔案 + final String docId = DocumentsContract.getDocumentId(uri); + final String[] divide = docId.split(":"); + final String type = divide[0]; + Uri mediaUri = null; + if ("image".equals(type)) { + mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + mediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } else { + return null; + } + mediaUri = ContentUris.withAppendedId(mediaUri, Long.parseLong(divide[1])); + String path = queryAbsolutePath(context, mediaUri); + return path; + } + } else { + // 如果是一般的URI + final String scheme = uri.getScheme(); + String path = null; + if ("content".equals(scheme)) { + // 內容URI + path = queryAbsolutePath(context, uri); + } else if ("file".equals(scheme)) { + // 檔案URI + path = uri.getPath(); + } + return path; + } + return null; + } + + public static String queryAbsolutePath(final Context context, final Uri uri) { + final String[] projection = {MediaStore.MediaColumns.DATA}; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + return cursor.getString(index); + } + } catch (final Exception ex) { + ex.printStackTrace(); + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + private ActivityResultLauncher getActivityResultLauncher(){ + return registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback(){ + // 重写回调方法 + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() != RESULT_OK) return; + ContentResolver resolver = getContentResolver(); + Uri originalUri = result.getData().getData(); //1.获得图片的真实路径 + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri));//2.解码图片 + } catch (FileNotFoundException e) { + Log.d(TAG, "onActivityResult: get file_exception"); + e.printStackTrace(); + } + + if (bitmap != null) { + //3.根据Bitmap对象创建ImageSpan对象 + Log.d(TAG, "onActivityResult: bitmap is not null"); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); + String path = getPathFromUri(NoteEditActivity.this, originalUri); + //4.使用[local][/local]将path括起来,用于之后方便识别图片路径在note中的位置 + String img_fragment = "" + path + ""; + //创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像 + SpannableString spannableString = new SpannableString(img_fragment); + spannableString.setSpan(imageSpan, 0, img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + //5.将选择的图片追加到EditText中光标所在位置 + NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view); + int index = e.getSelectionStart(); //获取光标所在位置 + Log.d(TAG, "Index是: " + index); + Editable edit_text = e.getEditableText(); + edit_text.insert(index, spannableString); //将图片插入到光标所在位置 + + mWorkingNote.mContent = e.getText().toString(); + //6.把改动提交到数据库中,两个数据库表都要改的 + ContentResolver contentResolver = getContentResolver(); + ContentValues contentValues = new ContentValues(); + final long id = mWorkingNote.getNoteId(); + contentValues.put("snippet", mWorkingNote.mContent); + contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id}); + ContentValues contentValues1 = new ContentValues(); + contentValues1.put("content", mWorkingNote.mContent); + contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id}); + + } + } + }); + } + /** + * @author: ClearDewy + * @date: 2023/3/10 12:43 + * @param: [] + * @return: androidx.activity.result.ActivityResultLauncher + * @description:当API>=33时,申请读取存储授权 + **/ + private ActivityResultLauncher getrequestDataLauncher(){ + return registerForActivityResult(new ActivityResultContracts.RequestPermission(), result -> { + if (result){ + Log.d(TAG,"用户已授权"); + convertToImage(); + }else{ + Log.d(TAG,"用户取消授权"); + } + }); + } + + //路径字符串格式 转换为 图片image格式 + private void convertToImage() { + NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view); //获取当前的edit + Editable editable = noteEditText.getText();//1.获取text + String noteText = editable.toString(); //2.将note内容转换为字符串 + int length = editable.length(); //内容的长度 + //3.截取img片段 [local]+uri+[local],提取uri + for(int i = 0; i < length; i++) { + for(int j = i; j < length; j++) { + String img_fragment = noteText.substring(i, j+1); //img_fragment:关于图片路径的片段 + if(img_fragment.length() > 15 && img_fragment.endsWith("[/local]") && img_fragment.startsWith("[local]")){ + int limit = 7; //[local]为7个字符 + //[local][/local]共15个字符,剩下的为真正的path长度 + int len = img_fragment.length()-15; + //从[local]之后的len个字符就是path + String path = img_fragment.substring(limit,limit+len);//获取到了图片路径 + + + } + } + } + + for (int i = 0; i < length; i++) { + if (noteText.startsWith("", i)){ + for(int j=i+5;j< length;j++){ + if (noteText.substring(i,j+1).endsWith("")){ + Bitmap bitmap = null; + String path=noteText.substring(i+5,j-5); + Log.d(TAG, "图片的路径是:"+path); + try { + bitmap = BitmapFactory.decodeFile(path);//将图片路径解码为图片格式 + } catch (Exception e) { + e.printStackTrace(); + } + if(bitmap!=null){ //若图片存在 + Log.d(TAG, "图片不为null"); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); + //4.创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像 + String ss = "" + path + ""; + SpannableString spannableString = new SpannableString(ss); + //5.将指定的标记对象附加到文本的开始...结束范围 + spannableString.setSpan(imageSpan, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + Log.d(TAG, "Create spannable string success!"); + Editable edit_text = noteEditText.getEditableText(); + edit_text.delete(i,j-i+1); //6.删掉图片路径的文字 + edit_text.insert(i, spannableString); //7.在路径的起始位置插入图片 + + i=j;break; + } + + } + } + } + } + + + + } + + + + // 添加图片按钮点击时触发 + private void onAddImage(ActivityResultLauncher intentActivityResultLauncher){ + Intent imgIntent=new Intent(Intent.ACTION_GET_CONTENT); + //Category属性用于指定当前动作(Action)被执行的环境. + //CATEGORY_OPENABLE; 用来指示一个ACTION_GET_CONTENT的intent + imgIntent.addCategory(Intent.CATEGORY_OPENABLE); + imgIntent.setType("image/*"); + intentActivityResultLauncher.launch(imgIntent); + } + } diff --git a/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index cb7246e..093a0d1 100644 --- a/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -72,19 +72,13 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; -import net.micode.notes.model.WorkingNote; import net.micode.notes.tool.BackupUtils; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.FingerprintDialogFragment; -import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.security.KeyStore; import java.util.HashSet; @@ -705,20 +699,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_new_note: - if(mCurrentFolderId==Notes.ID_TRASH_FOLER){ - Toast.makeText( - NotesListActivity.this, - R.string.forbidden_add_in_trash, - Toast.LENGTH_SHORT).show(); - }else { - createNewNote(); - } - break; - default: - break; - } + createNewNote(); } private void showSoftInput() { diff --git a/app/src/main/res/layout/note_edit.xml b/app/src/main/res/layout/note_edit.xml index ffea609..857e384 100644 --- a/app/src/main/res/layout/note_edit.xml +++ b/app/src/main/res/layout/note_edit.xml @@ -101,7 +101,8 @@ + android:layout_height="fill_parent" + android:orientation="horizontal"> + +