From 95185d7f3ccf5fa36d2e3343ef8b106e70b95199 Mon Sep 17 00:00:00 2001 From: zjb <2152698745@qq.com> Date: Mon, 30 Dec 2024 17:16:37 +0800 Subject: [PATCH] audio --- Src/app/src/main/AndroidManifest.xml | 2 + .../java/net/micode/notes/data/Notes.java | 2 + .../notes/data/NotesDatabaseHelper.java | 14 +- .../java/net/micode/notes/data/UserDao.java | 58 ++++-- .../net/micode/notes/login/LoginActivity.java | 20 +- .../micode/notes/login/RegisterActivity.java | 30 ++- .../java/net/micode/notes/model/Note.java | 9 +- .../net/micode/notes/model/WorkingNote.java | 65 +++++-- .../net/micode/notes/ui/NoteEditActivity.java | 176 +++++++++++++++++- .../micode/notes/ui/NotesListActivity.java | 103 +++++++--- Src/app/src/main/res/drawable/ic_audio.png | Bin 0 -> 3608 bytes Src/app/src/main/res/layout/note_edit.xml | 24 +++ Src/app/src/main/res/layout/note_list.xml | 12 ++ 13 files changed, 437 insertions(+), 78 deletions(-) create mode 100644 Src/app/src/main/res/drawable/ic_audio.png diff --git a/Src/app/src/main/AndroidManifest.xml b/Src/app/src/main/AndroidManifest.xml index 3915f9f..bc713e4 100644 --- a/Src/app/src/main/AndroidManifest.xml +++ b/Src/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ android:versionName="0.1"> + + diff --git a/Src/app/src/main/java/net/micode/notes/data/Notes.java b/Src/app/src/main/java/net/micode/notes/data/Notes.java index a053dfe..d8c643d 100644 --- a/Src/app/src/main/java/net/micode/notes/data/Notes.java +++ b/Src/app/src/main/java/net/micode/notes/data/Notes.java @@ -90,6 +90,8 @@ public class Notes { public static final String GTASK_ID = "gtask_id"; // Google任务ID (类型: TEXT) public static final String VERSION = "version"; // 版本号 (类型: INTEGER) public static final String IMAGE_PATH = "image_path";// 新增列存储图片路径 + public static final String USER_ID = "user_id"; + public static final String AUDIO_PATH = "audio_path"; } // DataColumns接口:定义数据列 diff --git a/Src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/Src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index 1a1633e..71c8826 100644 --- a/Src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/Src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -20,7 +20,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; // 数据库版本号,用于判断数据库结构是否需要升级 - private static final int DB_VERSION = 6; + private static final int DB_VERSION = 8; // 表接口,定义了数据库中两个主要表的表名:NOTE和DATA public interface TABLE { @@ -57,6 +57,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 文件夹内笔记数量 NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 笔记摘要 NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 笔记类型 + NoteColumns.USER_ID + " INTEGER NOT NULL DEFAULT -1," + // 新增 user_id 字段 NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型 NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID @@ -64,7 +65,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 原始父ID NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + // 笔记版本号 - NoteColumns.IMAGE_PATH + " TEXT" + // 新增列存储图片路径 + NoteColumns.IMAGE_PATH + " TEXT," + // 新增列存储图片路径 + NoteColumns.AUDIO_PATH + " TEXT" + //新增列存储音频路径 ")"; // SQL语句:创建DATA表 @@ -399,9 +401,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { // 在版本5中创建用户表 createUserTable(db); // 确保调用创建用户表的方法 } - if (oldVersion < 6) { // 假设你现在的版本是5 + if (oldVersion < 6) { // 增加照片路径新列 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IMAGE_PATH + " TEXT"); } + if(oldVersion < 7){ + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN "+ NoteColumns.USER_ID + "INTEGER NOT NULL DEFAULT -1"); + } + if(oldVersion < 8){ + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN "+ NoteColumns.AUDIO_PATH + " TEXT"); + } // 如果需要,重新创建触发器 reCreateNoteTableTriggers(db); // 重建NOTE表的触发器 diff --git a/Src/app/src/main/java/net/micode/notes/data/UserDao.java b/Src/app/src/main/java/net/micode/notes/data/UserDao.java index b6b86d9..5a80353 100644 --- a/Src/app/src/main/java/net/micode/notes/data/UserDao.java +++ b/Src/app/src/main/java/net/micode/notes/data/UserDao.java @@ -17,8 +17,8 @@ public class UserDao { this.dbHelper = dbHelper; } - // 注册用户的方法 - public boolean registerUser(String username, String password) { + // 注册用户,并返回用户 ID + public long registerUser(String username, String password) { SQLiteDatabase db = null; try { db = dbHelper.getWritableDatabase(); @@ -26,40 +26,58 @@ public class UserDao { values.put(COLUMN_USERNAME, username); values.put(COLUMN_PASSWORD, password); - // 尝试插入新用户 - long result = db.insert(NotesDatabaseHelper.TABLE.USER, null, values); + long userId = db.insert(NotesDatabaseHelper.TABLE.USER, null, values); - // 输出调试信息 - if (result == -1) { - Log.d("UserDao", "Registration failed for user: " + username); - return false; // 插入失败 + if (userId == -1) { + Log.d("UserDao", "注册失败,用户名:" + username); } else { - Log.d("UserDao", "Successfully registered user: " + username); - return true; // 插入成功 + Log.d("UserDao", "注册成功,用户ID:" + userId); } + return userId; // 返回用户ID } catch (Exception e) { - Log.e("UserDao", "Error registering user", e); - return false; // 发生异常 + Log.e("UserDao", "注册用户时出错", e); + return -1; // 插入失败 } finally { if (db != null) { - db.close(); // 确保数据库在操作完后关闭 + db.close(); // 确保数据库关闭 } } } + // 登录用户,返回用户 ID,失败返回 -1 + public int loginUser(String username, String password) { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + Cursor cursor = null; + try { + cursor = db.query(NotesDatabaseHelper.TABLE.USER, new String[]{"id"}, "username=? AND password=?", + new String[]{username, password}, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); // 返回用户ID + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1; // 登录失败 + } - - // 登录用户的方法 - public boolean loginUser(String username, String password) { + // 根据用户名获取用户 ID + public int getUserIdByUsername(String username) { SQLiteDatabase db = dbHelper.getReadableDatabase(); - Cursor cursor = null; // 初始化Cursor为空 + Cursor cursor = null; try { - cursor = db.query(NotesDatabaseHelper.TABLE.USER, null, "username=? AND password=?", new String[]{username, password}, null, null, null); - return cursor.getCount() > 0; // 如果查询结果超过0,表示用户存在 + cursor = db.query(NotesDatabaseHelper.TABLE.USER, new String[]{"id"}, "username=?", + new String[]{username}, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); // 返回用户ID + } } finally { if (cursor != null) { - cursor.close(); // 确保Cursor在使用完后关闭 + cursor.close(); } } + return -1; // 用户不存在 } + } \ No newline at end of file diff --git a/Src/app/src/main/java/net/micode/notes/login/LoginActivity.java b/Src/app/src/main/java/net/micode/notes/login/LoginActivity.java index 84e2281..a0b4ecf 100644 --- a/Src/app/src/main/java/net/micode/notes/login/LoginActivity.java +++ b/Src/app/src/main/java/net/micode/notes/login/LoginActivity.java @@ -1,7 +1,9 @@ package net.micode.notes.login; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -46,19 +48,31 @@ public class LoginActivity extends Activity { String password = etLoginPassword.getText().toString().trim(); if (username.isEmpty() || password.isEmpty()) { - showToast("Username or password cannot be empty"); + showToast("用户名或密码不能为空"); return; } - if (userDao.loginUser(username, password)) { + int userId = userDao.loginUser(username, password); + Log.d("LoginActivity", "Login userId: " + userId); + if (userId != -1) { // 登录成功 + setCurrentUserId(userId); // 保存用户ID到SharedPreferences + Intent intent = new Intent(LoginActivity.this, NotesListActivity.class); startActivity(intent); finish(); } else { - showToast("Invalid username or password"); + showToast("错误的用户名或密码"); } } + // 新增 setCurrentUserId 方法 + private void setCurrentUserId(int userId) { + Log.d("LoginActivity", "Saving userId to SharedPreferences: " + userId); + SharedPreferences.Editor editor = getSharedPreferences("user_session", MODE_PRIVATE).edit(); + editor.putInt("user_id", userId); // 保存当前用户ID + editor.apply(); + } + private void navigateToRegister() { Intent intent = new Intent(LoginActivity.this, RegisterActivity.class); startActivity(intent); diff --git a/Src/app/src/main/java/net/micode/notes/login/RegisterActivity.java b/Src/app/src/main/java/net/micode/notes/login/RegisterActivity.java index e61c2ae..a5abe3c 100644 --- a/Src/app/src/main/java/net/micode/notes/login/RegisterActivity.java +++ b/Src/app/src/main/java/net/micode/notes/login/RegisterActivity.java @@ -1,6 +1,7 @@ package net.micode.notes.login; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -13,6 +14,7 @@ import android.app.Activity; import net.micode.notes.R; import net.micode.notes.data.NotesDatabaseHelper; import net.micode.notes.data.UserDao; +import net.micode.notes.ui.NotesListActivity; public class RegisterActivity extends Activity { @@ -44,21 +46,35 @@ public class RegisterActivity extends Activity { String username = etUsername.getText().toString().trim(); String password = etPassword.getText().toString().trim(); - // 检查用户名和密码是否为空 if (username.isEmpty() || password.isEmpty()) { - showToast("Username or password cannot be empty"); + showToast("用户名或密码不能为空"); return; } - // 尝试注册用户 - if (userDao.registerUser(username, password)) { - showToast("User registered successfully"); - navigateToLogin(); // 注册成功后返回登录界面 + long userId = userDao.registerUser(username, password); + if (userId != -1) { // 注册成功 + showToast("注册成功"); + setCurrentUserId((int) userId); // 保存用户ID到SharedPreferences + navigateToNotesList(); // 跳转到主界面 } else { - showToast("Registration failed"); + showToast("注册失败,用户名可能已存在"); } } + // 新增 setCurrentUserId 方法 + private void setCurrentUserId(int userId) { + SharedPreferences.Editor editor = getSharedPreferences("user_session", MODE_PRIVATE).edit(); + editor.putInt("user_id", userId); // 保存当前用户ID + editor.apply(); + } + + // 新增 navigateToNotesList 方法 + private void navigateToNotesList() { + Intent intent = new Intent(RegisterActivity.this, NotesListActivity.class); + startActivity(intent); + finish(); + } + private void navigateToLogin() { Intent intent = new Intent(RegisterActivity.this, LoginActivity.class); startActivity(intent); diff --git a/Src/app/src/main/java/net/micode/notes/model/Note.java b/Src/app/src/main/java/net/micode/notes/model/Note.java index ceb150e..1a5f714 100644 --- a/Src/app/src/main/java/net/micode/notes/model/Note.java +++ b/Src/app/src/main/java/net/micode/notes/model/Note.java @@ -47,8 +47,7 @@ public class Note { * @param folderId 文件夹ID,表示新笔记将被添加到的文件夹 * @return 新创建的笔记的ID */ - public static synchronized long getNewNoteId(Context context, long folderId) { - // 在数据库中创建一个新的笔记 + public static synchronized long getNewNoteId(Context context, long folderId, int userId) { ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); values.put(NoteColumns.CREATED_DATE, createdTime); @@ -56,6 +55,10 @@ public class Note { values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.PARENT_ID, folderId); + values.put(NoteColumns.USER_ID, userId); // 设置 user_id + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); long noteId = 0; @@ -68,6 +71,8 @@ public class Note { if (noteId == -1) { throw new IllegalStateException("错误的笔记ID:" + noteId); } + Log.d("Note", "Inserted note for user_id: " + userId + ", noteId: " + noteId); + return noteId; } diff --git a/Src/app/src/main/java/net/micode/notes/model/WorkingNote.java b/Src/app/src/main/java/net/micode/notes/model/WorkingNote.java index b983b09..607cb85 100644 --- a/Src/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/Src/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -21,6 +21,7 @@ import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -177,9 +178,15 @@ public class WorkingNote { */ private void loadNote() { // 使用内容提供者查询数据库,获取与指定笔记ID匹配的笔记信息 + SharedPreferences prefs = mContext.getSharedPreferences("user_session", Context.MODE_PRIVATE); + int userId = prefs.getInt("user_id", -1); Cursor cursor = mContext.getContentResolver().query( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, - null, null); + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), + NOTE_PROJECTION, + NoteColumns.USER_ID + "=?", + new String[]{String.valueOf(userId)}, + null + ); if (cursor != null) { // 如果查询结果不为空,尝试读取数据 @@ -299,10 +306,21 @@ public class WorkingNote { if (isWorthSaving()) { // 检查笔记是否已存在于数据库中 if (!existInDatabase()) { - // 如果笔记不在数据库中,为该笔记生成一个新的ID - if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + // 获取当前登录用户的 user_id + SharedPreferences prefs = mContext.getSharedPreferences("user_session", Context.MODE_PRIVATE); + + int userId = prefs.getInt("user_id", -1); // -1 表示未登录 + Log.d("WorkingNote", "Saving note with userId: " + userId); + + if (userId == -1) { + Log.e(TAG, "无法保存笔记,因为用户未登录"); + return false; // 未登录时不保存 + } + + // 如果笔记不在数据库中,为该笔记生成一个新的ID,并关联 user_id + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId, userId)) == 0) { // 若生成新ID失败,记录日志并返回 false 表示保存失败 - Log.e(TAG, "Create new note fail with id:" + mNoteId); + Log.e(TAG, "创建新便签失败:" + mNoteId); return false; } } @@ -324,6 +342,7 @@ public class WorkingNote { } + /** * 检查笔记是否已存在于数据库中。 * @@ -665,17 +684,6 @@ public class WorkingNote { } } - private String getFileExtension(Uri imageUri) { - String mimeType = mContext.getContentResolver().getType(imageUri); - if (mimeType == null) return ".jpg"; // 默认使用jpg格式 - - String extension = mimeType.split("/")[1]; - if (extension.equals("jpeg")) return ".jpg"; - if (extension.equals("png")) return ".png"; - if (extension.equals("gif")) return ".gif"; - // 可以添加更多的图片格式 - return "." + extension; - } /** * 将图片路径保存到数据库。 @@ -716,5 +724,30 @@ public class WorkingNote { } return null; } + // 获取音频路径 + public String getAudioPathFromDatabase() { + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), + new String[]{NoteColumns.AUDIO_PATH}, null, null, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + String audioPath = cursor.getString(0); + cursor.close(); + return audioPath; + } + cursor.close(); + } + return null; + } + + // 保存音频路径到数据库 + public void saveAudioPathToDatabase(String audioPath) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.AUDIO_PATH, audioPath); + mContext.getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), values, null, null); + } + } diff --git a/Src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/Src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 3ae60b5..c629a5c 100644 --- a/Src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/Src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -16,12 +16,14 @@ package net.micode.notes.ui; +import android.Manifest; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; +import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -29,24 +31,30 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Paint; +import android.media.MediaPlayer; +import android.media.MediaRecorder; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.DocumentsContract; import android.provider.MediaStore; +import android.speech.RecognizerIntent; import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; +import android.text.method.LinkMovementMethod; import android.text.style.BackgroundColorSpan; +import android.text.style.ClickableSpan; import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; @@ -82,8 +90,10 @@ import net.micode.notes.widget.NoteWidgetProvider_4x; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -200,10 +210,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, // initResources(); // 初始化资源 // } private final int PHOTO_REQUEST=1; - @Override + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit); if (savedInstanceState == null && !initActivityState(getIntent())) { @@ -228,6 +239,33 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivityForResult(loadImage, PHOTO_REQUEST); } }); + // 根据id获取录音按钮 + final ImageButton record_audio_btn = (ImageButton) findViewById(R.id.record_audio_btn); + record_audio_btn.setOnClickListener(new View.OnClickListener() { + private boolean isRecording = false; + + @Override + public void onClick(View v) { + if (!isRecording) { + startRecordingAudio(); + isRecording = true; + record_audio_btn.setImageResource(android.R.drawable.ic_media_pause); // 切换为暂停图标 + } else { + stopRecordingAudio(); + isRecording = false; + record_audio_btn.setImageResource(android.R.drawable.ic_btn_speak_now); // 切换为录音图标 + } + } + }); + + // 根据id获取语音转文字按钮 + final ImageButton speech_to_text_btn = (ImageButton) findViewById(R.id.speech_to_text_btn); + speech_to_text_btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startSpeechToText(); + } + }); } /** * 当活动被系统销毁后,为了恢复之前的状态,此方法会被调用。 @@ -360,6 +398,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 获取图片路径并插入图片 String imagePath = mWorkingNote.getImagePathFromDatabase(); + String audioPath = mWorkingNote.getAudioPathFromDatabase(); if (imagePath != null) { // 从文件加载图像 Bitmap bitmap = mWorkingNote.loadImageFromFile(imagePath); @@ -376,11 +415,34 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { Log.e(TAG, "Failed to load image from file: " + imagePath); } - } else { + } + else { Log.e(TAG, "Image path is null. Can't load image."); } } + private MediaPlayer mediaPlayer; + private void playAudio(String path) { + if (mediaPlayer != null) { + mediaPlayer.release(); // 释放之前的实例 + mediaPlayer = null; + } + mediaPlayer = new MediaPlayer(); + try { + mediaPlayer.setDataSource(path); // 设置音频路径 + mediaPlayer.prepare(); // 准备播放 + mediaPlayer.start(); // 开始播放 + + // 设置播放完成的回调 + mediaPlayer.setOnCompletionListener(mp -> { + mp.release(); // 播放完成时释放资源 + mediaPlayer = null; + }); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(this, "Unable to play audio", Toast.LENGTH_SHORT).show(); + } + } /** * 初始化笔记界面的函数。 @@ -1292,7 +1354,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, // public boolean isDownloadsDocument(Uri uri) { // return "com.android.providers.downloads.documents".equals(uri.getAuthority()); // } - + private static final int REQUEST_RECORD_AUDIO = 101; + private static final int REQUEST_SPEECH_INPUT = 102; + private String audioFilePath; //是否为媒体文件 public boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); @@ -1327,6 +1391,31 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.e(TAG, "Selected image URI is null."); // 输出错误日志 Toast.makeText(this, "No image selected.", Toast.LENGTH_SHORT).show(); } + } // 音频录制处理逻辑 + else if (requestCode == REQUEST_RECORD_AUDIO && resultCode == RESULT_OK && intent != null) { + Uri audioUri = intent.getData(); + if (audioUri != null) { + String audioPath = getRealPathFromURI(audioUri); + mWorkingNote.saveAudioPathToDatabase(audioPath); + Toast.makeText(this, "Audio recorded successfully!", Toast.LENGTH_SHORT).show(); + Log.d(TAG, "Audio inserted successfully: " + audioPath); + } else { + Log.e(TAG, "Recorded audio URI is null."); + Toast.makeText(this, "Failed to record audio.", Toast.LENGTH_SHORT).show(); + } + } + // 语音转文字处理逻辑 + else if (requestCode == REQUEST_SPEECH_INPUT && resultCode == RESULT_OK && intent != null) { + ArrayList result = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); + if (result != null && !result.isEmpty()) { + String spokenText = result.get(0); + mNoteEditor.append(spokenText); + Toast.makeText(this, "Speech converted to text!", Toast.LENGTH_SHORT).show(); + Log.d(TAG, "Speech to text: " + spokenText); + } else { + Log.e(TAG, "Speech recognition result is null."); + Toast.makeText(this, "No speech recognized.", Toast.LENGTH_SHORT).show(); + } } else { Log.e(TAG, "Activity result error, requestCode: " + requestCode + ", resultCode: " + resultCode); } @@ -1359,4 +1448,85 @@ public class NoteEditActivity extends Activity implements OnClickListener, noteEditText.getEditableText().insert(index, spannableString); } + + private MediaRecorder mediaRecorder; + + private void startRecordingAudio() { + try { + audioFilePath = getExternalFilesDir(null).getAbsolutePath() + "/audio_" + System.currentTimeMillis() + ".3gp"; + + mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mediaRecorder.setOutputFile(audioFilePath); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + + mediaRecorder.prepare(); + mediaRecorder.start(); + + Toast.makeText(this, "Recording started...", Toast.LENGTH_SHORT).show(); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(this, "Recording failed!", Toast.LENGTH_SHORT).show(); + } + } + + private void stopRecordingAudio() { + if (mediaRecorder != null) { + mediaRecorder.stop(); + mediaRecorder.release(); + mediaRecorder = null; + + // 获取音频路径 + String audioPath = audioFilePath; + + // 插入到光标位置 + NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view); + int cursorPosition = noteEditText.getSelectionStart(); // 获取光标位置 + SpannableString spannableString = new SpannableString("[Audio]"); + spannableString.setSpan(new ImageSpan(this, R.drawable.ic_audio), 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + // 添加播放功能 + spannableString.setSpan(new ClickableSpan() { + @Override + public void onClick(View widget) { + Log.d("AudioClick", "Audio icon clicked"); + playAudio(audioPath); // 播放音频 + } + }, 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + Editable editable = noteEditText.getEditableText(); + editable.insert(cursorPosition, spannableString); // 插入到光标位置 + + // 保存音频路径到数据库 + mWorkingNote.saveAudioPathToDatabase(audioPath); + mWorkingNote.saveNote(); + // 确保编辑器启用点击事件 + noteEditText.setMovementMethod(LinkMovementMethod.getInstance()); + } + } + + + private String getRealPathFromURI(Uri contentUri) { + Cursor cursor = getContentResolver().query(contentUri, null, null, null, null); + if (cursor == null) return contentUri.getPath(); + cursor.moveToFirst(); + int idx = cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA); + String path = cursor.getString(idx); + cursor.close(); + return path; + } + + private void startSpeechToText() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()); + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak now..."); + try { + startActivityForResult(intent, REQUEST_SPEECH_INPUT); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, "Speech to text not supported", Toast.LENGTH_SHORT).show(); + } + } + } diff --git a/Src/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/Src/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index b53dfee..fb2f7ce 100644 --- a/Src/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/Src/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -64,6 +64,7 @@ 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.login.LoginActivity; import net.micode.notes.model.WorkingNote; import net.micode.notes.tool.BackupUtils; import net.micode.notes.tool.DataUtils; @@ -76,6 +77,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.HashSet; public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { @@ -173,10 +175,30 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt super.onCreate(savedInstanceState); setContentView(R.layout.note_list); initResources(); - + // 绑定登出按钮 + Button logoutButton = (Button) findViewById(R.id.btn_logout); + logoutButton.setOnClickListener(v -> logoutUser()); // 用户首次使用时插入介绍信息 setAppInfoFromRawRes(); } + @Override + protected void onResume() { + super.onResume(); + + // 检查当前用户是否已登录 + SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE); + int userId = prefs.getInt("user_id", -1); + + if (userId == -1) { + // 如果用户未登录,跳转到登录页面 + Intent intent = new Intent(NotesListActivity.this, LoginActivity.class); + startActivity(intent); + finish(); // 关闭当前页面 + } else { + // 启动查询以刷新便签列表 + startAsyncNotesListQuery(); + } + } /** * 处理从其他活动返回的结果。 @@ -548,16 +570,37 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt * 根据当前文件夹ID选择不同的查询条件,启动一个后台查询处理该查询。 */ private void startAsyncNotesListQuery() { - // 根据当前文件夹ID选择查询条件 - String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION - : NORMAL_SELECTION; - // 启动查询,排序方式为类型降序,修改日期降序 - mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, - Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[]{ - String.valueOf(mCurrentFolderId) - }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE); + int userId = prefs.getInt("user_id", -1); + if (userId == -1) { + Log.e(TAG, "No user_id found in SharedPreferences, redirecting to login"); + startActivity(new Intent(this, LoginActivity.class)); + finish(); + return; + } + + Log.d(TAG, "Loaded user_id: " + userId); + + // 添加 user_id 到查询条件 + String selection = NoteColumns.USER_ID + "=?"; + String[] selectionArgs = {String.valueOf(userId)}; + + mBackgroundQueryHandler.startQuery( + FOLDER_NOTE_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + null, + selection, + selectionArgs, + null + ); } + + + + + /** * 处理后台查询的类。 * 继承自AsyncQueryHandler,用于处理异步查询完成后的操作。 @@ -577,24 +620,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - switch (token) { - case FOLDER_NOTE_LIST_QUERY_TOKEN: - // 更新笔记列表适配器的数据源 - mNotesListAdapter.changeCursor(cursor); - break; - case FOLDER_LIST_QUERY_TOKEN: - // 根据查询结果展示或记录错误 - if (cursor != null && cursor.getCount() > 0) { - showFolderListMenu(cursor); - } else { - Log.e(TAG, "Query folder failed"); - } - break; - default: - // 对未知标记不做处理 + if (token == FOLDER_NOTE_LIST_QUERY_TOKEN) { + if (cursor == null || cursor.getCount() == 0) { + Log.e("BackgroundQueryHandler", "No notes found."); return; + } + + // 更新适配器的数据 + mNotesListAdapter.changeCursor(cursor); + mNotesListAdapter.notifyDataSetChanged(); + Log.d("BackgroundQueryHandler", "Adapter updated with new data."); } } + } /** @@ -1301,5 +1339,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.setPositiveButton(android.R.string.ok, null); builder.show(); } + private void logoutUser() { + // 清空 SharedPreferences 中的用户会话数据 + SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.clear(); // 清空所有存储的值 + editor.apply(); + + // 跳转到登录页面 + Intent intent = new Intent(NotesListActivity.this, LoginActivity.class); + startActivity(intent); + + // 销毁当前活动,避免返回后仍能访问 + finish(); + } + + + } diff --git a/Src/app/src/main/res/drawable/ic_audio.png b/Src/app/src/main/res/drawable/ic_audio.png new file mode 100644 index 0000000000000000000000000000000000000000..19b0d118c3fdd6710fa7fd828317ab6fac4f2f1c GIT binary patch literal 3608 zcmV+z4(IWSP)p2OuPVpa`TQRH;Jz04l8zq(y2QsW?@eP_?z&B#uKZ z$FK3sc;0iL=j^=~Kb$*bd+v;9oRHd1=Q|o5ox2}*@AcjLyVhRkTv1haar403=mG5F zYU~nnlVO*Tn+&^z++^4#9Q4AmQ2>SM_W zEdpO`{m-|~`1vD~wn0f8u&YpS2Z>nr*#^tnnfOBMr5E4yOOyN3v7vfHgfx-{|K5J( z^RMPp_upolyEObiBBJawb*95|{a;$gp2sFbb%qFedG#lIzt=tesYyG6?v#!QF*W$H z6hE306M?u(j6taXc!~O} zOW-ViUl~!o@k%Eft~UWJhCt+Gf94Y#%Z8l#PvYFKrnnvN4nLMse|4FC|9Y16LI)M- z-%(@kH}0TwZyh_|2l?q+L!uHBL##S%#}msI)AbOz;p8YvLrR>93lD588`>eWzO|>Z z!)VHnrc~cp<M{Nw z=df*0^MzSVHxNb>FhZY{76D5TeM;cHH(B{D{5kJt2ybV47_3%`y}l^ z;|B{!+f#pS8Pivi*&gB}$)z49i(t3kE)tYbF@&0hv>2BT#)UJ4MuJ))A5Bohj!0G% zqsDLie6thY&eu#-kiwx~<;0_+sX`2GUr^Bqd3 zTGU=yDGadzq2VZ>YBBoFDbm?4<6pf%s3l~3%BU5>o)ihn_*c$imvY9wF+)1lB~QjbJ%vy*f*!KSw!7i*TlDd~MeKZ!#`oqaoo!P&)uR66B|>BKU8g97hy;)bdc{uHLAgXlVo^?r zJ8~zX=BS=(Q+;(AH`}9nVgR1#%ftJWMWWap}*Ti zWTJo*q61My#G)}0om_wPB*k}QglGg4K}_= zbel20bwZ*N%Lb#0$p=g73KAqQcUq8{1>CXY-1{+ zkSY)5H`|zwr*d)`+YY2tU6Pfo2!6J~TP7q_9hFn7>{0mBI z@d{azfov!!vLi)(%aY}mbLM_!KNDY_!cSDV_}smu7up~~c6*Iv)nmJXOHUubEoPhc zA)$y!diRYGDvsIr^tnb=6lCS$RqMTn_E zREep@Hy!!@DzTB0PnPgCOYg24T6ScIs`!$ncV~@QHuMix2o+2Jo)Kcz7GrB9kZ*qk zkd5zUJ$vT!-)P77N32|%D~XD4rXGDEcx!5-b?6Xkw8(5-pgh zsI^Ea3?)D`X7Cld9vP_^&|()!EGnu34kTC9nqqM!g5&|U1`{D#L3}LAqZV@-m?08L zNRB>#*B^cGN|Ov(J=ah&J5Vw;f?Lk;70c|i2PrK@?)r=4L?UPY@)7b8hg-_ATamG5 z0i_54!oa|)h>t}eP#hix=Lb+(Yf{`86e6e=g0N;-6f_PD>LVHkf)QEoTV!xgajye; zk+Alh@A5(wjYXe>f24@^)HCR7FkUZ%kX(zPV-QLcVsVu#r!K_8i(ZAH4<>DG;f=gS zUCKKm#q=Y|e1@;5`09&DS6%yj%`j_|88p1&a=Go* z_gnQnbib|?x{21W)w$|+eOw#+T)+B_eP2edfu>EjPn|qmI{17C&-DfMf(qDvAn9I5 zs^n)Bg0QST?oIZ+vaxJvhum(PUl}pwf9z^TRgxWLJ|rm8_00U{orUCd!=?$qYj+|< zEzT}Gi$`u<1R{drJ~#1C8_R}v$OGo!!V~tMznu&1>-rMc39KGzviwjJ+YQ?{C@fz% zXH`@qNf2x_=)3q0BZDvoj#Le*5sRg)30d@Lh!~X{@|5wN zf{2i5;DTRb+|-u-pz*|`d+Q@hXRE_1G;|L6cJ?|^lv3BS82aYxy_0|STL1hXu4um+ zHL~{nr6MU+NeOEy?kjRxN|AvTF?a$!pHWgvPjieI5)54l)TAK6sNlqs$D9}=np&b^ zJ<7WEu%0Uh5qykPCE8_ecB<%YH?8Vnqy)BF?h|eYRCcwVYevO2+;u{IUcKh`oWV> zx-zcW5@P9(wWP%||}h zq02;4Vlt_b>n7!mdCl`?f4UIT(KN`;oLGnKXFmMK;qUL(RF%!C!jO?}~$jmO?UZmSn(eG5AfJ{h#T#iH07f+0fgOZIGDbKq3U z;^?Y(3%>PaNgR>LOlWgB-GAa!BaeT$AM=Z|p~WcW^TDu{-@EhpAp{d z&i(eed~rOY%!dw-q=)|bx#kn^-D~R?XJc!}`LA6JV`zuW`PP<|4Th-7x3klKJ|8>W zZN}Tb)p+Pn54q+)D3ME{wd=UwCWZ*P!#1|8JZT$zsq>@H{-A&U-e+nLe(Al*p-;c3 zG%@o^e~P*((b&OL#$8++LxfD5&EF77#4;5x-F+^uJoK@~`<{NL_VZsm*PCZLtP*$p elFKf(!~X$>3EcP4LLFuR0000 + + + + + + + + + +