diff --git a/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java b/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java index 2ad1765..eb03e7d 100644 --- a/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java +++ b/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java @@ -24,7 +24,8 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter audioFiles = new ArrayList<>(); - private OnDeleteClickListener onDeleteClickListener; + private AudioItem audioItem; + private OnOptionClickListener onOptionClickListener;; public A_ImportFragmentAdapter(Context context) { this.context = context; @@ -38,18 +39,12 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter { - if (onDeleteClickListener != null) { - onDeleteClickListener.onDeleteClick(audioItem.getFile()); - } - }); - holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -71,27 +66,45 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter= 0 && position < audioFiles.size()) { + audioFiles.get(position).setName(newTitle); // 假设 AudioItem 有一个 setName 方法 + notifyItemChanged(position); // 通知 RecyclerView 更新该项 + } } - public interface OnDeleteClickListener { - void onDeleteClick(String filePath); + + public void setOnOptionClickListener(OnOptionClickListener listener) { + this.onOptionClickListener = listener; } - public static class ViewHolder extends RecyclerView.ViewHolder { + public interface OnOptionClickListener { + void onOptionClick(int position, String filePath, View anchorView); + } - ImageView deleteButton; + + class ViewHolder extends RecyclerView.ViewHolder { + + ImageView option; TextView title; TextView time; public ViewHolder(@NonNull View itemView) { super(itemView); - deleteButton = itemView.findViewById(R.id.options); + option = itemView.findViewById(R.id.options); title = itemView.findViewById(R.id.title); time = itemView.findViewById(R.id.time); + option.setOnClickListener(v -> { + int position = getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION && onOptionClickListener != null) { + onOptionClickListener.onOptionClick(position, audioItem.getFile(), option); + } + }); + + + } } diff --git a/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java b/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java index 6edab85..f9eeccb 100644 --- a/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java +++ b/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java @@ -5,6 +5,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.res.AssetFileDescriptor; import android.media.MediaPlayer; @@ -12,6 +13,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.PowerManager; import android.util.Log; import androidx.annotation.Nullable; @@ -19,6 +21,7 @@ import androidx.core.app.NotificationCompat; import androidx.lifecycle.MutableLiveData; import com.hi.music.player.R; +import com.hi.music.player.javabean.A_data.AudioItem; import com.hi.music.player.ui.activity.A_PlayActivity; import java.io.File; @@ -26,15 +29,18 @@ import java.io.IOException; public class MusicPlayerForegroundService extends Service { - // 通知渠道ID,用于创建通知渠道 + // 通知渠道ID public static final String CHANNEL_ID = "MusicPlayerChannel"; - private MediaPlayer mediaPlayer; // MediaPlayer实例,用于播放音频 + private MediaPlayer mediaPlayer; // 播放音频的MediaPlayer实例 private final IBinder binder = new MusicBinder(); // 用于绑定服务的Binder - private final MutableLiveData isPlaying = new MutableLiveData<>(false); // 用于跟踪播放状态的LiveData - private final MutableLiveData fileName = new MutableLiveData<>(); // 保存当前播放的文件名 - private String currentAudioPath = null; // 用于保存当前正在播放的音频路径 + private final MutableLiveData isPlaying = new MutableLiveData<>(false); // 播放状态 + private final MutableLiveData fileName = new MutableLiveData<>(); // 当前播放的文件名 + private AudioItem audioItem; // 当前播放的音频项 + private String currentAudioPath = null; // 当前播放的音频路径 + private PowerManager.WakeLock wakeLock; // 用于防止设备休眠 + private boolean isLooping = true; // 循环播放标志 - // 自定义Binder类,允许客户端绑定到服务 + // 自定义Binder类 public class MusicBinder extends Binder { public MusicPlayerForegroundService getService() { return MusicPlayerForegroundService.this; // 返回当前服务实例 @@ -44,192 +50,203 @@ public class MusicPlayerForegroundService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { - return binder; // 返回用于绑定服务的Binder + return binder; // 返回Binder } - // 当服务启动时调用该方法 + // 服务启动时调用 @Override public int onStartCommand(Intent intent, int flags, int startId) { - // 从Intent中获取传入的音频路径 - String newAudioPath = intent.getStringExtra("Path"); + Log.d("MusicPlayerService", "Service started"); + audioItem = (AudioItem) intent.getSerializableExtra("Path"); // 获取音频路径 - if (newAudioPath != null) { - // 如果传入的音频路径与当前播放路径不同,则重新初始化播放器 - if (currentAudioPath == null || !newAudioPath.equals(currentAudioPath)) { + if (audioItem != null) { + String newAudioPath = audioItem.getFile(); + // 如果新音频路径不同于当前路径,则重新初始化播放器 + if (newAudioPath != null && !newAudioPath.equals(currentAudioPath)) { Log.d("MusicPlayerService", "检测到新的音频路径,初始化播放器"); - currentAudioPath = newAudioPath; // 更新当前播放路径 - stopAndResetMediaPlayer(); // 停止并重置当前的播放器状态 - initializePlayer(newAudioPath); // 初始化播放器并开始播放新的音频 + currentAudioPath = newAudioPath; // 更新路径 + stopAndResetMediaPlayer(); // 停止并重置播放器 + initializePlayer(newAudioPath); // 初始化播放器 } else { Log.d("MusicPlayerService", "音频路径相同,无需重新初始化播放器"); } } else { - stopSelf(); // 如果路径为空,停止服务 + stopSelf(); // 路径为空,停止服务 } - // 启动前台服务并显示通知 - startForeground(1, createNotification()); - return START_NOT_STICKY; // 返回START_NOT_STICKY,表示服务不会在被系统杀死后自动重启 + startForeground(1, createNotification()); // 启动前台服务并显示通知 + acquireWakeLock(); // 获取WakeLock + + return START_NOT_STICKY; // 服务不会在被杀死后自动重启 } - // 创建通知,用于前台服务显示 + // 创建通知 private Notification createNotification() { - createNotificationChannel(); // 创建通知渠道(适用于Android O及以上版本) - - // 创建点击通知时打开的Intent + createNotificationChannel(); // 创建通知渠道 Intent notificationIntent = new Intent(this, A_PlayActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0); // 适配Android 12 - // 构建通知,设置标题、内容和小图标 return new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("Playing Audio") // 通知标题 - .setContentText("Your audio is playing") // 通知内容 - .setSmallIcon(R.drawable.home_select) // 通知的小图标 - .setContentIntent(pendingIntent) // 点击通知后的Intent - .setPriority(NotificationCompat.PRIORITY_LOW) // 低优先级,避免干扰用户 + .setContentTitle("正在播放音频") // 通知标题 + .setContentText("您的音频正在播放") // 通知内容 + .setSmallIcon(R.drawable.home_select) // 小图标 + .setContentIntent(pendingIntent) // 点击通知的Intent + .setPriority(NotificationCompat.PRIORITY_LOW) // 低优先级 .build(); } - // 创建通知渠道,适用于Android O及以上版本 + // 创建通知渠道 private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel serviceChannel = new NotificationChannel( - CHANNEL_ID, "Music Player Channel", NotificationManager.IMPORTANCE_LOW); + CHANNEL_ID, "音乐播放器频道", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { - manager.createNotificationChannel(serviceChannel); // 创建通知渠道 + manager.createNotificationChannel(serviceChannel); // 创建渠道 } } } - // 停止并重置播放器,确保在播放新的音频时播放器状态正确 - private void stopAndResetMediaPlayer() { + // 停止并重置播放器 + public void stopAndResetMediaPlayer() { if (mediaPlayer != null) { if (mediaPlayer.isPlaying()) { - mediaPlayer.stop(); // 停止正在播放的音频 + mediaPlayer.stop(); // 停止播放 } - mediaPlayer.reset(); // 重置播放器状态,使其可以播放新的音频 + mediaPlayer.reset(); // 重置播放器 } } - // 初始化媒体播放器并设置数据源 + // 初始化播放器 private void initializePlayer(String path) { try { Log.d("MusicPlayerService", "正在初始化播放器,音频路径: " + path); - if (mediaPlayer == null) { - mediaPlayer = new MediaPlayer(); // 如果MediaPlayer为空,则创建一个新的实例 + mediaPlayer = new MediaPlayer(); // 创建新的MediaPlayer实例 } - // 设置当前播放的文件名 - fileName.setValue(getFileNameFromPath(path)); + fileName.setValue(getFileNameFromPath(path)); // 设置当前文件名 + setDataSource(path); // 设置数据源 - // 根据传入的音频路径类型设置数据源 - setDataSource(path); - - mediaPlayer.prepareAsync(); // 异步准备播放器 + mediaPlayer.prepareAsync(); // 异步准备 mediaPlayer.setOnPreparedListener(mp -> { mediaPlayer.start(); // 播放器准备好后开始播放 - isPlaying.postValue(true); // 更新播放状态为“播放中” + isPlaying.postValue(true); // 更新播放状态 }); - // 设置播放完成后的监听器 + // 播放完成监听 mediaPlayer.setOnCompletionListener(mp -> { - isPlaying.postValue(false); // 更新播放状态为“未播放” - stopSelf(); // 播放完成后停止服务 + if (isLooping) { + mediaPlayer.seekTo(0); // 循环播放 + mediaPlayer.start(); + } else { + isPlaying.postValue(false); // 更新播放状态 + stopSelf(); // 播放完成后停止服务 + } }); - // 设置播放错误的监听器 + // 播放错误监听 mediaPlayer.setOnErrorListener((mp, what, extra) -> { Log.e("MusicPlayerService", "播放时发生错误: " + what + ", 额外信息: " + extra); - isPlaying.postValue(false); // 更新播放状态为“未播放” + isPlaying.postValue(false); // 更新状态 stopSelf(); // 播放出错后停止服务 - return true; // 返回true表示错误已经被处理 + return true; // 表示错误已处理 }); } catch (IOException | IllegalArgumentException | IllegalStateException e) { Log.e("MusicPlayerService", "初始化播放器失败,路径: " + path, e); - isPlaying.postValue(false); // 如果初始化失败,更新状态为“未播放” - stopSelf(); // 初始化失败后停止服务 + isPlaying.postValue(false); // 更新状态 + stopSelf(); // 停止服务 } } - // 设置数据源,根据音频路径的类型来加载音频 + // 设置数据源 private void setDataSource(String path) throws IOException { if (path.startsWith("content://")) { - setDataSourceFromContentUri(path); // 如果是内容URI,使用内容URI加载 + setDataSourceFromContentUri(path); // 使用内容URI加载 } else { - setDataSourceFromFileOrAsset(path); // 否则尝试从文件或资产目录中加载 + setDataSourceFromFileOrAsset(path); // 从文件或资产加载 } } - // 使用内容URI加载音频数据源 + // 使用内容URI加载音频 private void setDataSourceFromContentUri(String path) throws IOException { - Uri contentUri = Uri.parse(path); // 将字符串路径解析为URI + Uri contentUri = Uri.parse(path); // 解析URI try (AssetFileDescriptor afd = getContentResolver().openAssetFileDescriptor(contentUri, "r")) { if (afd != null) { - // 使用文件描述符和偏移量设置MediaPlayer的数据源 - mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); + mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); // 设置数据源 } else { throw new IOException("无法打开内容URI: " + path); } + } catch (SecurityException e) { + Log.e("MusicPlayerService", "权限被拒绝,无法访问内容URI: " + path, e); + throw new IOException("权限被拒绝", e); // 处理权限异常 } } - // 尝试从文件或assets目录加载音频数据源 + // 从文件或assets加载音频 private void setDataSourceFromFileOrAsset(String path) throws IOException { try { - // 尝试从assets目录加载音频 - AssetFileDescriptor afd = getAssets().openFd(path); + AssetFileDescriptor afd = getAssets().openFd(path); // 从assets加载 mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); } catch (IOException e) { - // 如果assets加载失败,则尝试从文件系统加载音频 File file = new File(path); if (file.exists()) { - mediaPlayer.setDataSource(file.getAbsolutePath()); // 从文件路径加载 + mediaPlayer.setDataSource(file.getAbsolutePath()); // 从文件加载 } else { throw new IOException("文件未找到: " + path); } } } - // 根据路径获取文件名 + // 获取文件名 + // 获取文件名(不带扩展名) private String getFileNameFromPath(String path) { try { - return new File(path).getName(); // 从路径中提取文件名 + // 创建File对象并获取文件名 + String fileName = new File(path).getName(); + // 找到最后一个点的位置 + int dotIndex = fileName.lastIndexOf('.'); + // 如果找到了点,返回不带扩展名的文件名 + return (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName; } catch (Exception e) { Log.e("MusicPlayerService", "获取文件名失败", e); - return "未知文件"; // 如果失败,返回默认文件名 + return "未知文件"; // 默认文件名 } } - // 暂停音频播放 + + // 暂停播放 public void pauseAudio() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { - mediaPlayer.pause(); // 如果当前正在播放,则暂停音频 - isPlaying.postValue(false); // 更新播放状态为“暂停” + mediaPlayer.pause(); // 暂停 + isPlaying.postValue(false); // 更新状态 } } - // 恢复音频播放 + // 恢复播放 public void resumeAudio() { if (mediaPlayer != null && !mediaPlayer.isPlaying()) { - mediaPlayer.start(); // 如果当前未播放,则恢复音频播放 - isPlaying.postValue(true); // 更新播放状态为“播放中” + mediaPlayer.start(); // 恢复播放 + isPlaying.postValue(true); // 更新状态 } } - // 获取当前播放状态 + // 获取播放状态 public MutableLiveData getIsPlaying() { return isPlaying; } - // 获取当前播放的文件名 + // 获取当前文件名 public MutableLiveData getFileName() { return fileName; } + public String getName() { + return mediaPlayer != null ? audioItem.getName() : null; + } + // 获取当前播放位置(毫秒) public int getCurrentPosition() { return mediaPlayer != null ? mediaPlayer.getCurrentPosition() : 0; @@ -243,17 +260,32 @@ public class MusicPlayerForegroundService extends Service { // 跳转到指定播放位置 public void seekTo(int position) { if (mediaPlayer != null) { - mediaPlayer.seekTo(position); // 将播放器跳转到指定位置 + mediaPlayer.seekTo(position); // 跳转到指定位置 } } - // 当服务销毁时释放MediaPlayer资源 + // 获取WakeLock + private void acquireWakeLock() { + PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "MusicPlayer::WakeLock"); + wakeLock.acquire(10 * 60 * 1000L); // 获取WakeLock,防止设备休眠 + } + } + + // 销毁服务时释放资源 @Override public void onDestroy() { super.onDestroy(); + // 释放WakeLock + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + wakeLock = null; // 释放后设置为null + } if (mediaPlayer != null) { mediaPlayer.release(); // 释放MediaPlayer资源 - mediaPlayer = null; // 将播放器设置为null,防止再次使用 + mediaPlayer = null; // 设置为null } } } diff --git a/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java index 9fdf64d..179ce6a 100644 --- a/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java +++ b/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java @@ -22,194 +22,198 @@ import java.util.Locale; public class A_HomeActivity extends BaseActivity { - private final int[] defaultIcons = { R.drawable.home_unselect, R.drawable.import_unselect }; - private final int[] selectedIcons = { R.drawable.home_select, R.drawable.import_select }; + private MusicPlayerForegroundService musicService; // 音乐播放服务 + private boolean isBound = false; // 服务是否绑定 + private Handler progressHandler; // 用于更新进度的Handler - private MusicPlayerForegroundService musicService; - private boolean isBound = false; - private Handler progressHandler; + private final int[] defaultIcons = {R.drawable.home_unselect, R.drawable.import_unselect}; // 默认图标 + private final int[] selectedIcons = {R.drawable.home_select, R.drawable.import_select}; // 选中图标 + + @Override + protected ActivityAhomeBinding getViewBinding() { + return ActivityAhomeBinding.inflate(getLayoutInflater()); // 获取视图绑定 + } private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService(); - isBound = true; - setupObservers(); - startUpdatingProgress(); + musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService(); // 获取服务实例 + isBound = true; // 服务已绑定 + updatePlayButtonState(); // 更新播放按钮状态 + startUpdatingProgress(); // 启动进度更新 + observeIsPlaying(); // 观察播放状态变化 } @Override public void onServiceDisconnected(ComponentName name) { - isBound = false; + isBound = false; // 服务已断开 } }; - @Override - protected ActivityAhomeBinding getViewBinding() { - return ActivityAhomeBinding.inflate(getLayoutInflater()); - } - @Override protected void onCreateInit() { Intent serviceIntent = new Intent(this, MusicPlayerForegroundService.class); - startService(serviceIntent); - bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); - initData(); + startService(serviceIntent); // 启动音乐播放服务 + bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); // 绑定服务 + + // 初始化界面 + vb.pause.setOnClickListener(v -> togglePlayPause()); // 设置暂停/播放按钮点击事件 + setupProgressBar(); // 设置进度条 + setupViewPager(); // 设置ViewPager + setupTabLayout(); // 设置TabLayout } @Override protected void onInitClick() { - + // 处理其他点击事件 } @Override public boolean isFullScreen() { - return false; + return false; // 不是全屏模式 } @Override public boolean statusBarLight() { - return false; + return false; // 状态栏不为浅色 } - private void initData() { - setupViewPager(); - setupTabLayout(); - setupProgressBar(); - setupPauseButton(); + // 设置进度条 + private void setupProgressBar() { + vb.circularProgressBar.setMaxProgress(100); // 设置进度条最大值 } + // 切换播放/暂停 + private void togglePlayPause() { + if (isBound && musicService != null) { + // 判断当前播放状态并切换 + if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) { + musicService.pauseAudio(); // 暂停播放 + } else { + musicService.resumeAudio(); // 恢复播放 + } + updatePlayButtonState(); // 更新播放按钮图标 + } + } + + // 更新播放按钮的状态 + private void updatePlayButtonState() { + if (isBound && musicService != null && musicService.getIsPlaying().getValue() != null) { + boolean isPlaying = musicService.getIsPlaying().getValue(); // 获取播放状态 + vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play); // 更新图标 + } + } + + // 更新进度 + private void updateProgress() { + if (isBound && musicService != null) { + int currentPosition = musicService.getCurrentPosition(); // 当前播放位置 + int duration = musicService.getDuration(); // 音频总时长 + String fileName = musicService.getFileName().getValue(); // 当前文件名 + String name = musicService.getName();//// 当前文件名 + + vb.bottomText1.setText(formatTime(currentPosition)); // 更新当前时间文本 + vb.bottomText2.setText(formatTime(duration)); // 更新总时间文本 + vb.topText.setText(name); // 更新文件名文本 + + if (duration > 0) { + int progress = (int) ((currentPosition / (float) duration) * 100); // 计算进度 + vb.circularProgressBar.setProgress(progress); // 更新进度条 + } + } + } + + // 启动进度更新 + private void startUpdatingProgress() { + if (progressHandler != null) return; // 如果已存在Handler,则返回 + progressHandler = new Handler(Looper.getMainLooper()); + progressHandler.postDelayed(new Runnable() { + @Override + public void run() { + updateProgress(); // 更新进度 + progressHandler.postDelayed(this, 1000); // 每秒更新一次 + } + }, 1000); + } + + // 观察播放状态变化 + private void observeIsPlaying() { + if (isBound && musicService != null) { + musicService.getIsPlaying().observe(this, isPlaying -> { + if (isPlaying != null) { + vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play); // 更新播放按钮图标 + } + }); + } + } + + // 设置ViewPager private void setupViewPager() { - A_HomeViewPagerAdapter adapter = new A_HomeViewPagerAdapter(this); - vb.homeViewpager.setAdapter(adapter); - vb.homeViewpager.setUserInputEnabled(false); + A_HomeViewPagerAdapter adapter = new A_HomeViewPagerAdapter(this); // 创建适配器 + vb.homeViewpager.setAdapter(adapter); // 设置适配器 + vb.homeViewpager.setUserInputEnabled(false); // 禁用用户输入 } + // 设置TabLayout private void setupTabLayout() { new TabLayoutMediator(vb.homeTabLayout, vb.homeViewpager, (tab, position) -> { - HomeTabCustomBinding tabBinding = HomeTabCustomBinding.inflate(LayoutInflater.from(this)); - tab.setCustomView(tabBinding.getRoot()); - tabBinding.homeIcon.setImageResource(defaultIcons[position]); - }).attach(); + HomeTabCustomBinding tabBinding = HomeTabCustomBinding.inflate(LayoutInflater.from(this)); // 自定义Tab布局 + tab.setCustomView(tabBinding.getRoot()); // 设置自定义视图 + tabBinding.homeIcon.setImageResource(defaultIcons[position]); // 设置默认图标 + }).attach(); // 附加TabLayout和ViewPager vb.homeTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { - updateTabIcon(tab, true); + updateTabIcon(tab, true); // 更新选中图标 } @Override public void onTabUnselected(TabLayout.Tab tab) { - updateTabIcon(tab, false); + updateTabIcon(tab, false); // 更新未选中图标 } @Override - public void onTabReselected(TabLayout.Tab tab) {} + public void onTabReselected(TabLayout.Tab tab) { + // 不需要处理 + } }); // 默认选择第一个标签 TabLayout.Tab firstTab = vb.homeTabLayout.getTabAt(0); if (firstTab != null) { - firstTab.select(); - updateTabIcon(firstTab, true); + firstTab.select(); // 选择第一个标签 + updateTabIcon(firstTab, true); // 更新选中图标 } } - private void setupProgressBar() { - vb.circularProgressBar.setMaxProgress(100); - vb.circularProgressBar.setOnProgressChangeListener((progressBar, progress) -> { - if (isBound && musicService != null) { - musicService.seekTo((int) ((progress / 100.0) * musicService.getDuration())); - } - }); - } - - private void setupPauseButton() { - vb.pause.setOnClickListener(v -> { - if (isBound && musicService != null) { - if (Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) { - musicService.pauseAudio(); - } else { - musicService.resumeAudio(); - } - } - }); - } - - private void setupObservers() { - if (musicService == null || isBound == false) return; // 确保只在服务绑定时设置观察者 - - musicService.getIsPlaying().observe(this, isPlaying -> { - if (isPlaying != null) { - vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play); - if (isPlaying) { - startUpdatingProgress(); // 只有在播放时才开始更新进度 - } else { - stopUpdatingProgress(); // 暂停更新 - } - } - }); - } - - private void startUpdatingProgress() { - if (progressHandler != null) return; // 确保只有一个 Handler 在工作 - progressHandler = new Handler(Looper.getMainLooper()); - progressHandler.postDelayed(updateProgressRunnable, 1000); - } - - private void stopUpdatingProgress() { - if (progressHandler != null) { - progressHandler.removeCallbacks(updateProgressRunnable); - progressHandler = null; - } - } - - private final Runnable updateProgressRunnable = new Runnable() { - @Override - public void run() { - if (isBound && musicService != null && Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) { - updateProgressAndDuration(); - progressHandler.postDelayed(this, 1000); - } - } - }; - - private void updateProgressAndDuration() { - int currentPosition = musicService.getCurrentPosition(); - int duration = musicService.getDuration(); - if (duration > 0) { - vb.circularProgressBar.setProgress((int) ((currentPosition / (float) duration) * 100)); - vb.bottomText.setText(formatTime(currentPosition)); - vb.topText.setText(formatTime(duration)); - } - } - - private String formatTime(int milliseconds) { - int minutes = (milliseconds / 1000) / 60; - int seconds = (milliseconds / 1000) % 60; - return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds); - } - + // 更新Tab图标 private void updateTabIcon(TabLayout.Tab tab, boolean isSelected) { View customView = tab.getCustomView(); if (customView != null) { HomeTabCustomBinding tabBinding = HomeTabCustomBinding.bind(customView); - tabBinding.homeIcon.setImageResource(isSelected ? selectedIcons[tab.getPosition()] : defaultIcons[tab.getPosition()]); + tabBinding.homeIcon.setImageResource(isSelected ? selectedIcons[tab.getPosition()] : defaultIcons[tab.getPosition()]); // 更新图标 } } @Override protected void onDestroy() { super.onDestroy(); - if (isBound) { - unbindService(serviceConnection); - isBound = false; + unbindService(serviceConnection); // 解绑服务 + if (progressHandler != null) { + progressHandler.removeCallbacksAndMessages(null); // 移除所有回调 } - stopUpdatingProgress(); // 确保停止更新进度 + } + + // 格式化时间 + private String formatTime(int milliseconds) { + int minutes = (milliseconds / 1000) / 60; // 计算分钟 + int seconds = (milliseconds / 1000) % 60; // 计算秒数 + return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds); // 格式化为"分钟:秒" } @Override public void onClick(View v) { - // Handle additional click events if needed + // 处理其他点击事件 } } diff --git a/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java index 13d5b19..ad62eca 100644 --- a/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java +++ b/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java @@ -4,45 +4,58 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.graphics.Color; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.widget.RadioGroup; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.hi.music.player.R; import com.hi.music.player.databinding.ActivityAplayBinding; import com.hi.music.player.javabean.A_data.AudioItem; import com.hi.music.player.service.MusicPlayerForegroundService; -import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; public class A_PlayActivity extends BaseActivity { - private MusicPlayerForegroundService musicService; - private boolean isBound = false; - private Handler progressHandler; + private MusicPlayerForegroundService musicService; // 音乐播放服务 + private boolean isBound = false; // 服务绑定状态 + private boolean isBackground; // 背景状态 + private Handler progressHandler; // 用于更新进度的Handler + private List textViews = new ArrayList<>(); // 存储变换颜色的文字 + // 服务连接对象 private final ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - MusicPlayerForegroundService.MusicBinder binder = (MusicPlayerForegroundService.MusicBinder) service; - musicService = binder.getService(); + musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService(); isBound = true; - // 只设置一次观察者 + // 设置观察者 musicService.getIsPlaying().observe(A_PlayActivity.this, this::updatePlayButton); - musicService.getFileName().observe(A_PlayActivity.this, vb.songTitle::setText); +// musicService.getFileName().observe(A_PlayActivity.this, vb.songTitle::setText); + vb.songTitle.setText(musicService.getName()); - startUpdatingProgress(); + startUpdatingProgress(); // 启动进度更新 } @Override public void onServiceDisconnected(ComponentName name) { - isBound = false; + isBound = false; // 服务断开时更新状态 } + // 更新播放按钮状态 private void updatePlayButton(Boolean isPlaying) { if (isPlaying != null) { vb.playButton.setBackgroundResource(isPlaying ? R.drawable.pause : R.drawable.play); @@ -57,34 +70,46 @@ public class A_PlayActivity extends BaseActivity { @Override protected void onCreateInit() { - Intent intent = getIntent(); - AudioItem audioItem = (AudioItem) intent.getSerializableExtra("Path"); + textViews.add(vb.songTitle); + textViews.add(vb.artistAlbumText); + textViews.add(vb.current); + textViews.add(vb.time); + textViews.add(vb.nowPlayingText); + + loadBackgroundPreference(); // 加载之前保存的背景状态 + + // 从Intent获取音频项 + AudioItem audioItem = (AudioItem) getIntent().getSerializableExtra("Path"); if (audioItem == null) { - finish(); + finish(); // 若未获取到音频路径则结束Activity return; } - startMusicService(audioItem.getFile()); - setupPlayButtonClickListener(); - vb.songSeekbar.setOnSeekBarChangeListener(new SeekBarListener()); + startMusicService(audioItem); // 启动音乐服务 + setupPlayButtonClickListener(); // 设置播放按钮的点击事件 } @Override protected void onInitClick() { - + vb.songSeekbar.setOnSeekBarChangeListener(new SeekBarListener()); // 设置进度条监听 + vb.repeatButton.setOnClickListener(v -> showTimerDialog()); // 设置定时器按钮的点击事件 + vb.prevButton.setOnClickListener(v -> toggleBackground()); // 设置切换背景按钮的点击事件 } - private void startMusicService(String path) { + // 启动音乐服务并绑定 + private void startMusicService(AudioItem audioItem) { Intent serviceIntent = new Intent(this, MusicPlayerForegroundService.class); - serviceIntent.putExtra("Path", path); - Log.d("A_PlayActivity", "Path: " + path); - startService(serviceIntent); - bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); + serviceIntent.putExtra("Path", audioItem); + Log.d("A_PlayActivity", "Path: " + audioItem); + startService(serviceIntent); // 启动服务 + bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); // 绑定服务 } + // 设置播放按钮的点击事件 private void setupPlayButtonClickListener() { vb.playButton.setOnClickListener(v -> { if (isBound && musicService != null) { + // 根据播放状态切换播放/暂停 if (Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) { musicService.pauseAudio(); } else { @@ -93,27 +118,30 @@ public class A_PlayActivity extends BaseActivity { } }); - vb.backButton.setOnClickListener(v -> finish()); + vb.backButton.setOnClickListener(v -> finish()); // 返回按钮点击事件 } + // 启动进度更新 private void startUpdatingProgress() { if (progressHandler == null) { progressHandler = new Handler(Looper.getMainLooper()); progressHandler.postDelayed(new Runnable() { @Override public void run() { - updateSeekBarAndTime(); - progressHandler.postDelayed(this, 1000); + updateSeekBarAndTime(); // 更新进度条和时间 + progressHandler.postDelayed(this, 1000); // 每秒更新 } }, 1000); } } + // 更新进度条和时间显示 private void updateSeekBarAndTime() { if (isBound && musicService != null && Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) { - int currentPosition = musicService.getCurrentPosition(); - int duration = musicService.getDuration(); + int currentPosition = musicService.getCurrentPosition(); // 当前播放进度 + int duration = musicService.getDuration(); // 音频总时长 if (duration > 0) { + // 更新进度条和时间显示 vb.songSeekbar.setProgress((int) ((currentPosition / (float) duration) * 100)); vb.current.setText(formatTime(currentPosition)); vb.time.setText(formatTime(duration)); @@ -121,6 +149,90 @@ public class A_PlayActivity extends BaseActivity { } } + // 切换背景 + private void toggleBackground() { + isBackground = !isBackground; // 切换背景状态 + if (isBackground) { + vb.rootLayout.setBackgroundResource(R.color.dark_music); // 切换为暗色背景 + vb.backButton.setImageResource(R.drawable.arrow_left); + setTextColor(Color.WHITE); // 设置文字颜色为白色 + } else { + vb.rootLayout.setBackgroundResource(R.color.white); // 切换为亮色背景 + vb.backButton.setImageResource(R.drawable.arrow_left_black); + setTextColor(Color.BLACK); // 设置文字颜色为黑色 + } + saveBackgroundPreference(); // 保存当前背景状态 + } + + // 保存背景状态到SharedPreferences + private void saveBackgroundPreference() { + SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean("isBackground", isBackground); + editor.apply(); + } + + // 从SharedPreferences加载背景状态 + private void loadBackgroundPreference() { + SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE); + isBackground = sharedPreferences.getBoolean("isBackground", true); // 默认值为 true + + // 根据保存的状态设置背景 + vb.rootLayout.setBackgroundResource(isBackground ? R.color.dark_music : R.color.white); + vb.backButton.setImageResource(isBackground ? R.drawable.arrow_left : R.drawable.arrow_left_black); + setTextColor(isBackground ? Color.WHITE : Color.BLACK); + } + + // 设置文字颜色 + private void setTextColor(int color) { + for (TextView textView : textViews) { + textView.setTextColor(color); + } + } + + // 显示定时器对话框 + private void showTimerDialog() { + BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this); + View dialogView = LayoutInflater.from(this).inflate(R.layout.timing_dialog, null); + bottomSheetDialog.setContentView(dialogView); + + RadioGroup timerOptions = dialogView.findViewById(R.id.timerOptions); + timerOptions.setOnCheckedChangeListener((group, checkedId) -> { + int duration = 0; // 用于保存定时时长 + + if (checkedId == R.id.radioOff) { + Toast.makeText(getApplicationContext(), "定时关闭已取消", Toast.LENGTH_SHORT).show(); + return; // 取消定时 + } else if (checkedId == R.id.radio10) { + duration = 10 * 60 * 1000; // 10分钟 + } else if (checkedId == R.id.radio20) { + duration = 20 * 60 * 1000; // 20分钟 + } else if (checkedId == R.id.radio30) { + duration = 30 * 60 * 1000; // 30分钟 + } else if (checkedId == R.id.radio60) { + duration = 60 * 60 * 1000; // 60分钟 + } else if (checkedId == R.id.radio90) { + duration = 90 * 60 * 1000; // 90分钟 + } + if (duration > 0) { + Toast.makeText(getApplicationContext(), "定时设置为 " + (duration / 60000) + " 分钟", Toast.LENGTH_SHORT).show(); + startTimer(duration); // 启动定时器 + } + }); + + bottomSheetDialog.show(); // 显示 BottomSheetDialog + } + + // 启动定时器 + private void startTimer(int duration) { + // 使用 Handler 来设置定时器 + new Handler().postDelayed(() -> { + musicService.pauseAudio(); + Toast.makeText(getApplicationContext(), "定时器结束", Toast.LENGTH_SHORT).show(); + }, duration); + } + + // 格式化时间显示 private String formatTime(int milliseconds) { int minutes = (milliseconds / 1000) / 60; int seconds = (milliseconds / 1000) % 60; @@ -131,51 +243,52 @@ public class A_PlayActivity extends BaseActivity { protected void onDestroy() { super.onDestroy(); if (isBound) { - unbindService(serviceConnection); + unbindService(serviceConnection); // 解绑服务 isBound = false; } if (progressHandler != null) { - progressHandler.removeCallbacksAndMessages(null); + progressHandler.removeCallbacksAndMessages(null); // 移除所有更新任务 progressHandler = null; // 释放引用 } } + // 进度条的监听器 + private class SeekBarListener implements SeekBar.OnSeekBarChangeListener { + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && musicService != null && isBound) { + int duration = musicService.getDuration(); + if (duration > 0) { + int newPosition = (int) ((progress / 100.0) * duration); + musicService.seekTo(newPosition); // 设置音频进度 + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // 用户开始拖动进度条时触发 + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + startUpdatingProgress(); // 拖动结束后重新启动进度更新 + } + } + @Override public boolean isFullScreen() { - return true; + return true; // 设置为全屏 } @Override public boolean statusBarLight() { - return false; + return false; // 设置状态栏为非亮色 } @Override public void onClick(View v) { // 处理其他点击事件 } - - private class SeekBarListener implements android.widget.SeekBar.OnSeekBarChangeListener { - @Override - public void onProgressChanged(android.widget.SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser && musicService != null && isBound) { - int duration = musicService.getDuration(); - if (duration > 0) { - int newPosition = (int) ((progress / 100.0) * duration); - musicService.seekTo(newPosition); - } - } - } - - @Override - public void onStartTrackingTouch(android.widget.SeekBar seekBar) { - // 用户开始拖动进度条时触发 - } - - @Override - public void onStopTrackingTouch(android.widget.SeekBar seekBar) { - // 用户停止拖动进度条时触发 - startUpdatingProgress(); // 拖动结束后重新启动进度更新 - } - } } diff --git a/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMHome.java b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMHome.java new file mode 100644 index 0000000..f51e8eb --- /dev/null +++ b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMHome.java @@ -0,0 +1,18 @@ +package com.hi.music.player.ui.activity.viewmodel; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.hi.music.player.service.MusicPlayerForegroundService; + +public class A_VMHome extends AndroidViewModel { + + + public A_VMHome(@NonNull Application application) { + super(application); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java b/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java index f42667c..a8ffc37 100644 --- a/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java +++ b/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java @@ -3,11 +3,22 @@ package com.hi.music.player.ui.fragmnt; import static android.app.Activity.RESULT_OK; import android.Manifest; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -16,6 +27,7 @@ import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; +import com.hi.music.player.R; import com.hi.music.player.adapter.A_ImportFragmentAdapter; import com.hi.music.player.databinding.FragmentAImportBinding; import com.hi.music.player.javabean.A_data.AudioItem; @@ -24,11 +36,13 @@ import com.hi.music.player.ui.fragmnt.viewmodel.A_VMImport; import java.io.IOException; import java.util.List; +import java.util.Objects; public class A_ImportFragment extends BaseFragment { private static final int REQUEST_CODE_READ_MEDIA_AUDIO = 1001; private static final int REQUEST_CODE_PICK_AUDIO = 1002; + private A_ImportFragmentAdapter adapter; private A_VMImport viewModel; @@ -42,65 +56,137 @@ public class A_ImportFragment extends BaseFragment { viewModel = new ViewModelProvider(this).get(A_VMImport.class); adapter = new A_ImportFragmentAdapter(requireContext()); - Vb.importRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - Vb.importRecycler.setAdapter(adapter); - - // 监听音频文件列表变化 - viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList); - + setupRecyclerView(); + observeAudioFiles(); initEvent(); } - // 更新音频文件列表 + private void setupRecyclerView() { + Vb.importRecycler.setLayoutManager(new LinearLayoutManager(getContext())); + Vb.importRecycler.setAdapter(adapter); + } + + private void observeAudioFiles() { + viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList); + } + private void updateAudioList(List audioFiles) { adapter.setAudioFiles(audioFiles); } - // 初始化事件 - public void initEvent() { - Vb.setting.setOnClickListener(v -> { - Intent intent = new Intent(getContext(), A_SettingActivity.class); - startActivity(intent); - }); - - // 添加按钮的点击事件,打开文件选择器 + private void initEvent() { + Vb.setting.setOnClickListener(v -> startActivity(new Intent(getContext(), A_SettingActivity.class))); Vb.add.setOnClickListener(v -> checkAndOpenAudioPicker()); - - // 适配器中的删除按钮点击事件 - adapter.setOnDeleteClickListener(filePath -> { - viewModel.markAudioAsDeleted(filePath); - Toast.makeText(getContext(), "Audio marked as deleted", Toast.LENGTH_SHORT).show(); - }); + adapter.setOnOptionClickListener(this::showDialog); + } + + private void showDialog(int position, String filePath, View anchorView) { + Dialog dialog = createOptionsDialog(anchorView); + Button buttonOne = dialog.findViewById(R.id.rename); + Button buttonTwo = dialog.findViewById(R.id.delete); + + buttonOne.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog.dismiss(); + showRenameDialog(position); + } + }); + + buttonTwo.setOnClickListener(v -> deleteAudioFile(position, filePath, dialog)); + dialog.show(); + } + + + private Dialog createOptionsDialog(View anchorView) { + Dialog dialog = new Dialog(requireContext()); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setContentView(R.layout.options_dialog); + dialog.setCancelable(true); + Objects.requireNonNull(dialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setDialogPosition(dialog, anchorView); // 设置对话框的位置 + return dialog; + } + + + private void setDialogPosition(Dialog dialog, View anchorView) { + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(layoutParams); + + // 获取按钮在屏幕上的位置 + int[] location = new int[2]; + anchorView.getLocationOnScreen(location); + + // 设置对话框在按钮旁边显示 + layoutParams.x = location[0] + anchorView.getWidth(); // 在按钮右侧显示 + layoutParams.y = location[1]; // 与按钮的 Y 坐标对齐 + window.setAttributes(layoutParams); + } + } + + + private void showRenameDialog(int position) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.rename_dialog, null); + builder.setView(dialogView); + + AlertDialog subDialog = builder.create(); + Objects.requireNonNull(subDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + + EditText inputField = dialogView.findViewById(R.id.input_field); + TextView cancel = dialogView.findViewById(R.id.cancel); + TextView confirm = dialogView.findViewById(R.id.confirm); + + confirm.setOnClickListener(view -> { + String newName = inputField.getText().toString(); + + // 更新适配器中的音频文件名称 + adapter.updateTitle(position, newName); + + Toast.makeText(requireContext(), "新名称: " + newName + ", 项目: " + position, Toast.LENGTH_SHORT).show(); + subDialog.dismiss(); + }); + + cancel.setOnClickListener(view -> subDialog.dismiss()); + + subDialog.show(); + } + + private void deleteAudioFile(int position, String filePath, Dialog dialog) { + viewModel.markAudioAsDeleted(filePath); + Toast.makeText(requireContext(), "删除按钮被点击, 项目: " + position, Toast.LENGTH_SHORT).show(); + dialog.dismiss(); } - // 检查并请求权限,然后打开音频选择器 private void checkAndOpenAudioPicker() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(requireActivity(), - new String[]{Manifest.permission.READ_MEDIA_AUDIO}, REQUEST_CODE_READ_MEDIA_AUDIO); + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(Manifest.permission.READ_MEDIA_AUDIO); } else { - openAudioPicker(); // 权限已授予,打开选择器 + openAudioPicker(); } } else { - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(requireActivity(), - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_MEDIA_AUDIO); + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE); } else { - openAudioPicker(); // 权限已授予,打开选择器 + openAudioPicker(); } } } - // 处理权限请求结果 + private void requestPermissions(String permission) { + ActivityCompat.requestPermissions(requireActivity(), new String[]{permission}, REQUEST_CODE_READ_MEDIA_AUDIO); + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_CODE_READ_MEDIA_AUDIO) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // 权限已授予,打开音频选择器 openAudioPicker(); } else { Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show(); @@ -108,50 +194,39 @@ public class A_ImportFragment extends BaseFragment { } } - // 打开音频选择器 private void openAudioPicker() { - // 检查外部存储状态 - String state = Environment.getExternalStorageState(); - if (!Environment.MEDIA_MOUNTED.equals(state)) { + if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { Toast.makeText(getContext(), "外部存储不可用", Toast.LENGTH_LONG).show(); return; } Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 持久 URI 权限 - intent.setType("audio/*"); // 设置 MIME 类型为音频 - intent.addCategory(Intent.CATEGORY_OPENABLE); // 限制为可打开的文件 - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予 URI 读取权限 + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + intent.setType("audio/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_PICK_AUDIO); } - // 处理音频选择器结果 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK) { - if (data != null) { - Uri selectedAudioUri = data.getData(); - if (selectedAudioUri != null) { - // 授予 URI 读取权限 - requireActivity().grantUriPermission(requireActivity().getPackageName(), selectedAudioUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - - // 将音频添加到 ViewModel 中 - try { - viewModel.addAudioFile(selectedAudioUri); - } catch (IOException e) { - throw new RuntimeException(e); - } + if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK && data != null) { + Uri selectedAudioUri = data.getData(); + if (selectedAudioUri != null) { + requireActivity().grantUriPermission(requireActivity().getPackageName(), selectedAudioUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + viewModel.addAudioFile(selectedAudioUri); + } catch (IOException e) { + throw new RuntimeException(e); } } } } - // 刷新音频列表 @Override public void onResume() { super.onResume(); - // 重新获取音频文件列表并更新 UI - viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList); + observeAudioFiles(); } } diff --git a/app/src/main/res/drawable/arrow_left_black.xml b/app/src/main/res/drawable/arrow_left_black.xml new file mode 100644 index 0000000..6dd116f --- /dev/null +++ b/app/src/main/res/drawable/arrow_left_black.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/rounded_edittext_background.xml b/app/src/main/res/drawable/rounded_edittext_background.xml new file mode 100644 index 0000000..9d32a0a --- /dev/null +++ b/app/src/main/res/drawable/rounded_edittext_background.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/timer_option_selector.xml b/app/src/main/res/drawable/timer_option_selector.xml new file mode 100644 index 0000000..ea9c9e5 --- /dev/null +++ b/app/src/main/res/drawable/timer_option_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_ahome.xml b/app/src/main/res/layout/activity_ahome.xml index d11b637..b7afc1b 100644 --- a/app/src/main/res/layout/activity_ahome.xml +++ b/app/src/main/res/layout/activity_ahome.xml @@ -34,6 +34,7 @@ android:layout_marginBottom="-5dp" android:background="@drawable/round_rectangle" android:backgroundTint="#80F988" + android:clickable="true" app:layout_constraintBottom_toTopOf="@+id/home_tab_layout"> @@ -54,7 +55,6 @@ android:layout_height="wrap_content" android:layout_gravity="center" /> - + + + + diff --git a/app/src/main/res/layout/options_dialog.xml b/app/src/main/res/layout/options_dialog.xml new file mode 100644 index 0000000..7d7fe6b --- /dev/null +++ b/app/src/main/res/layout/options_dialog.xml @@ -0,0 +1,35 @@ + + + +