@ -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 < String > 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 < Intent > 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 < Intent > getActivityResultLauncher ( ) {
return registerForActivityResult ( new ActivityResultContracts . StartActivityForResult ( ) , new ActivityResultCallback < ActivityResult > ( ) {
// 重写回调方法
@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 = "<img>" + path + "</img>" ;
//创建一个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 < java . lang . String >
* @description : 当 API > = 33 时 , 申 请 读 取 存 储 授 权
* * /
private ActivityResultLauncher < String > 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 ( "<img>" , i ) ) {
for ( int j = i + 5 ; j < length ; j + + ) {
if ( noteText . substring ( i , j + 1 ) . endsWith ( "</img>" ) ) {
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 = "<img>" + path + "</img>" ;
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 < Intent > 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 ) ;
}
}