From 1f5be70add32ffca90d649307854eb3d4e8e5aa8 Mon Sep 17 00:00:00 2001 From: JCode-JCode Date: Sun, 7 Jun 2026 17:58:07 +0330 Subject: [PATCH] Add Professional music player to Ide --- .../activity/FileManagerActivity.java | 731 +++++++++--------- .../MusicPlayerBottomSheetFragment.java | 156 ++++ .../ghostide/utils/MusicPlayerManager.java | 184 +++++ .../res/drawable/bottom_sheet_background.xml | 5 + app/src/main/res/drawable/ic_music.png | Bin 0 -> 631 bytes .../main/res/layout/activity_filemanager.xml | 137 ++-- .../main/res/layout/fragment_music_player.xml | 73 ++ app/src/main/res/menu/menu_filemanager.xml | 9 + 8 files changed, 869 insertions(+), 426 deletions(-) create mode 100644 app/src/main/java/ir/hanzodev1375/ghostide/fragments/MusicPlayerBottomSheetFragment.java create mode 100644 app/src/main/java/ir/hanzodev1375/ghostide/utils/MusicPlayerManager.java create mode 100644 app/src/main/res/drawable/bottom_sheet_background.xml create mode 100644 app/src/main/res/drawable/ic_music.png create mode 100644 app/src/main/res/layout/fragment_music_player.xml create mode 100644 app/src/main/res/menu/menu_filemanager.xml diff --git a/app/src/main/java/ir/hanzodev1375/ghostide/activity/FileManagerActivity.java b/app/src/main/java/ir/hanzodev1375/ghostide/activity/FileManagerActivity.java index 7841ed3..f5e9d46 100644 --- a/app/src/main/java/ir/hanzodev1375/ghostide/activity/FileManagerActivity.java +++ b/app/src/main/java/ir/hanzodev1375/ghostide/activity/FileManagerActivity.java @@ -9,429 +9,450 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; -import androidx.activity.EdgeToEdge; + import androidx.activity.OnBackPressedCallback; -import androidx.appcompat.view.ActionMode; +import androidx.appcompat.widget.Toolbar; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; + import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.skydoves.powermenu.MenuAnimation; import com.skydoves.powermenu.PowerMenu; import com.skydoves.powermenu.PowerMenuItem; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import ir.hanzodev1375.components.RenameDialogFragment; import ir.hanzodev1375.components.TextInputDialogFragment; +import ir.hanzodev1375.ghostide.R; import ir.hanzodev1375.ghostide.adapters.FileManagerAdapter; import ir.hanzodev1375.ghostide.databinding.ActivityFilemanagerBinding; import ir.hanzodev1375.ghostide.databinding.SelectionPanelBinding; +import ir.hanzodev1375.ghostide.fragments.MusicPlayerBottomSheetFragment; import ir.hanzodev1375.ghostide.models.FileManagerModel; import ir.hanzodev1375.ghostide.mvvm.viewmodel.FileViewModel; import ir.hanzodev1375.ghostide.plugin.PluginManager; import ir.hanzodev1375.ghostide.utils.MarginItemDecoration; +import ir.hanzodev1375.ghostide.utils.MusicPlayerManager; +import ir.hanzodev1375.ghostide.utils.PermissionUtils; import ir.hanzodev1375.ghostide.utils.ShapeUtil; import ir.theme.themeeditor.ThemeEditorActivity; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import ir.hanzodev1375.ghostide.R; -import java.util.Set; public class FileManagerActivity extends BaseCompat { - private ActivityFilemanagerBinding bind; - private FileViewModel viewModel; - private FileManagerAdapter adapter; - private View selectionPanel; - private TextView selectionCount; - private ImageView btnCopy, btnCut, btnDelete, btnPaste, btnClose, btnSelectall; - private boolean isCutOperation = false; - private List pendingClipboard = new ArrayList<>(); - private SelectionPanelBinding selectionPanelBinding; - private Set itemname = - new HashSet<>(Arrays.asList(".html", ".java", ".cpp", ".css", ".js")); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - bind = ActivityFilemanagerBinding.inflate(getLayoutInflater()); - setContentView(bind.getRoot()); - setupInsets(); - new Handler(Looper.getMainLooper()) - .postDelayed( - () -> { - try { - PluginManager.getInstance().setCurrentFileManagerActivity(this); - } catch (Exception e) { - e.printStackTrace(); - } - }, - 100); - bind.headline.setBackground(ShapeUtil.shape(40f, this)); - viewModel = new ViewModelProvider(this).get(FileViewModel.class); - adapter = new FileManagerAdapter(this); - bind.rvfiles.setLayoutManager(new LinearLayoutManager(this)); - bind.rvfiles.setAdapter(adapter); - bind.rvfiles.addItemDecoration(new MarginItemDecoration(this)); - - adapter.setupSelectionTracker(bind.rvfiles); - - viewModel - .getFiles() - .observe( - this, - files -> { - adapter.submitList(new ArrayList<>(files)); - bind.rvfiles.post( - () -> { - adapter.notifyDataSetChanged(); - }); - if (files == null || files.isEmpty()) { + private ActivityFilemanagerBinding bind; + private FileViewModel viewModel; + private FileManagerAdapter adapter; + private View selectionPanel; + private TextView selectionCount; + private ImageView btnCopy, btnCut, btnDelete, btnPaste, btnClose, btnSelectall; + private boolean isCutOperation = false; + private List pendingClipboard = new ArrayList<>(); + private SelectionPanelBinding selectionPanelBinding; + private Set itemname = new HashSet<>(Arrays.asList(".html", ".java", ".cpp", ".css", ".js")); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + bind = ActivityFilemanagerBinding.inflate(getLayoutInflater()); + setContentView(bind.getRoot()); + setupInsets(); + + Toolbar toolbar = bind.toolbar; + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle("مدیریت فایل"); + } + + new Handler(Looper.getMainLooper()) + .postDelayed(() -> { + try { + PluginManager.getInstance().setCurrentFileManagerActivity(this); + } catch (Exception e) { + e.printStackTrace(); + } + }, 100); + bind.headline.setBackground(ShapeUtil.shape(40f, this)); + viewModel = new ViewModelProvider(this).get(FileViewModel.class); + adapter = new FileManagerAdapter(this); + bind.rvfiles.setLayoutManager(new LinearLayoutManager(this)); + bind.rvfiles.setAdapter(adapter); + bind.rvfiles.addItemDecoration(new MarginItemDecoration(this)); + + adapter.setupSelectionTracker(bind.rvfiles); + + viewModel.getFiles().observe(this, files -> { + adapter.submitList(new ArrayList<>(files)); + bind.rvfiles.post(() -> adapter.notifyDataSetChanged()); + if (files == null || files.isEmpty()) { bind.emptystates.setVisibility(View.VISIBLE); bind.rvfiles.setVisibility(View.GONE); - } else { + } else { bind.emptystates.setVisibility(View.GONE); bind.rvfiles.setVisibility(View.VISIBLE); - } - }); - viewModel - .getIsLoading() - .observe( - this, loading -> bind.loadingprogass.setVisibility(loading ? View.VISIBLE : View.GONE)); - viewModel.savePath(true); - viewModel - .getCurrentPath() - .observe( - this, - path -> { - if (path != null) { + } + }); + viewModel.getIsLoading().observe(this, loading -> bind.loadingprogass.setVisibility(loading ? View.VISIBLE : View.GONE)); + viewModel.savePath(true); + viewModel.getCurrentPath().observe(this, path -> { + if (path != null) { bind.navmodel.setFile(new File(path)); - } - }); - - adapter.setOnItemClickListener( - (item, pos) -> { - if (item.isDirectory()) { - viewModel.navigateTo(item.getPath()); - } else { - setupClick(item.getPath(), item.getName()); - } + } }); - bind.fab.setOnClickListener( - v -> startActivity(new Intent(FileManagerActivity.this, SettingActivity.class))); - bind.navmodel - .getAdapter() - .setOnItemClickListener((view, nav, pos) -> viewModel.navigateTo(nav.getFilePath())); - stepMoreAdapter(); - setupSelectionPanel(); - adapter.setSelectionStateListener( - new FileManagerAdapter.SelectionStateListener() { - @Override - public void onSelectionChanged(int count) { - - if (count == 0 && pendingClipboard.isEmpty()) { - if (selectionPanel != null) selectionPanel.setVisibility(View.GONE); - } else if (count > 0) { - selectionPanel.setVisibility(View.VISIBLE); - selectionCount.setText("Item select " + String.valueOf(count)); - } else if (count == 0 && !pendingClipboard.isEmpty()) { - - selectionCount.setText("0"); - selectionPanel.setVisibility(View.VISIBLE); + adapter.setOnItemClickListener((item, pos) -> { + if (item.isDirectory()) { + viewModel.navigateTo(item.getPath()); + } else { + setupClick(item.getPath(), item.getName()); } - } + }); - @Override - public void onSelectionModeStarted() {} + bind.fab.setOnClickListener(v -> startActivity(new Intent(FileManagerActivity.this, SettingActivity.class))); + bind.navmodel.getAdapter().setOnItemClickListener((view, nav, pos) -> viewModel.navigateTo(nav.getFilePath())); + stepMoreAdapter(); + setupSelectionPanel(); + adapter.setSelectionStateListener(new FileManagerAdapter.SelectionStateListener() { + @Override + public void onSelectionChanged(int count) { + if (count == 0 && pendingClipboard.isEmpty()) { + if (selectionPanel != null) selectionPanel.setVisibility(View.GONE); + } else if (count > 0) { + selectionPanel.setVisibility(View.VISIBLE); + selectionCount.setText("Item select " + count); + } else if (count == 0 && !pendingClipboard.isEmpty()) { + selectionCount.setText("0"); + selectionPanel.setVisibility(View.VISIBLE); + } + } - @Override - public void onSelectionModeEnded() { + @Override + public void onSelectionModeStarted() {} - if (pendingClipboard.isEmpty() && selectionPanel != null) { - selectionPanel.setVisibility(View.GONE); + @Override + public void onSelectionModeEnded() { + if (pendingClipboard.isEmpty() && selectionPanel != null) { + selectionPanel.setVisibility(View.GONE); + } } - } }); - setOnBackPress(); - } - - void setupClick(String path, String name) { - int lastDot = name.lastIndexOf("."); - String extension = (lastDot > 0) ? name.substring(lastDot).toLowerCase() : ""; - if (itemname.contains(extension)) { - Intent intent = new Intent(FileManagerActivity.this, EditorActivity.class); - intent.putExtra("file_path", path); - intent.putExtra("file_name", name); - startActivity(intent); - } else if (path.endsWith(".gth")) { - Intent i = new Intent(FileManagerActivity.this, ThemeEditorActivity.class); - i.putExtra(ThemeEditorActivity.EXTRA_THEME_PATH, path); - startActivity(i); - - } else { - Toast.makeText(this, "فرمت فایل پشتیبانی نمی‌شود", Toast.LENGTH_SHORT).show(); + setOnBackPress(); } - } - - private void setupSelectionPanel() { - selectionPanelBinding = bind.selectionPanel; - selectionPanel = selectionPanelBinding.getRoot(); - - selectionCount = selectionPanelBinding.txtSelectedCount; - btnCopy = selectionPanelBinding.btnCopy; - btnCut = selectionPanelBinding.btnCut; - btnDelete = selectionPanelBinding.btnDelete; - btnPaste = selectionPanelBinding.btnPaste; - btnClose = selectionPanelBinding.btnClose; - btnSelectall = selectionPanelBinding.btnSelectall; - selectionPanelBinding.getRoot().setBackground(ShapeUtil.shapeCustomView(this)); - btnCopy.setOnClickListener( - v -> { - List selected = adapter.getSelectedItems(); - if (!selected.isEmpty()) { - pendingClipboard = new ArrayList<>(selected); - isCutOperation = false; - adapter.clearSelection(); - btnPaste.setColorFilter(0xff00ff00); - selectionPanel.setVisibility(View.VISIBLE); - selectionCount.setText("0"); - } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_filemanager, menu); + new Handler(Looper.getMainLooper()).post(() -> { + Toolbar toolbar = bind.toolbar; + if (toolbar != null) { + View musicItemView = toolbar.findViewById(R.id.action_music); + if (musicItemView != null) { + musicItemView.setOnLongClickListener(v -> { + stopMusic(); + return true; + }); + } + } }); + return true; + } - btnCut.setOnClickListener( - v -> { - List selected = adapter.getSelectedItems(); - if (!selected.isEmpty()) { - pendingClipboard = new ArrayList<>(selected); - isCutOperation = true; - adapter.clearSelection(); - btnPaste.setColorFilter(0xff00ff00); - selectionPanel.setVisibility(View.VISIBLE); - selectionCount.setText("0"); - } - }); - btnDelete.setOnClickListener( - v -> { - List selected = adapter.getSelectedItems(); - if (!selected.isEmpty()) { - new MaterialAlertDialogBuilder(this) - .setTitle("Delete") - .setMessage("Delete " + selected.size() + " items?") - .setPositiveButton( - "Delete", - (d, w) -> { - viewModel.deleteFiles(selected); - adapter.clearSelection(); - }) - .setNegativeButton("Cancel", null) + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_music) { + showMusicChooserDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void showMusicChooserDialog() { + if (!PermissionUtils.hasPermissions(this)) { + PermissionUtils.requestPermissions(this); + Toast.makeText(this, "لطفاً مجوزهای لازم را بدهید", Toast.LENGTH_SHORT).show(); + return; + } + + List musicPaths = getMusicFilesList(); + if (musicPaths.isEmpty()) { + Toast.makeText(this, "هیچ فایل موسیقی یافت نشد", Toast.LENGTH_SHORT).show(); + return; + } + + List musicNames = new ArrayList<>(); + for (String path : musicPaths) { + musicNames.add(new File(path).getName()); + } + + new MaterialAlertDialogBuilder(this) + .setTitle("انتخاب آهنگ") + .setItems(musicNames.toArray(new String[0]), (dialog, which) -> { + String selectedPath = musicPaths.get(which); + MusicPlayerManager.getInstance(this).play(selectedPath); + MusicPlayerBottomSheetFragment bottomSheet = new MusicPlayerBottomSheetFragment(); + bottomSheet.show(getSupportFragmentManager(), "MusicPlayer"); + }) + .setNegativeButton("لغو", null) .show(); - } + } + + private List getMusicFilesList() { + List songs = new ArrayList<>(); + android.database.Cursor cursor = null; + try { + android.net.Uri collection = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + String[] projection = {android.provider.MediaStore.Audio.Media.DATA}; + cursor = getContentResolver().query(collection, projection, null, null, null); + if (cursor != null) { + int dataCol = cursor.getColumnIndexOrThrow(android.provider.MediaStore.Audio.Media.DATA); + while (cursor.moveToNext()) { + String path = cursor.getString(dataCol); + if (path != null && new File(path).exists()) { + songs.add(path); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) cursor.close(); + } + return songs; + } + + private void stopMusic() { + MusicPlayerManager player = MusicPlayerManager.getInstance(this); + if (player.isPlaying()) { + player.stop(); + Toast.makeText(this, "پخش موسیقی متوقف شد", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "هیچ موسیقی در حال پخش نیست", Toast.LENGTH_SHORT).show(); + } + } + + void setupClick(String path, String name) { + int lastDot = name.lastIndexOf("."); + String extension = (lastDot > 0) ? name.substring(lastDot).toLowerCase() : ""; + if (itemname.contains(extension)) { + Intent intent = new Intent(FileManagerActivity.this, EditorActivity.class); + intent.putExtra("file_path", path); + intent.putExtra("file_name", name); + startActivity(intent); + } else if (path.endsWith(".gth")) { + Intent i = new Intent(FileManagerActivity.this, ThemeEditorActivity.class); + i.putExtra(ThemeEditorActivity.EXTRA_THEME_PATH, path); + startActivity(i); + } else { + Toast.makeText(this, "فرمت فایل پشتیبانی نمی‌شود", Toast.LENGTH_SHORT).show(); + } + } + + private void setupSelectionPanel() { + selectionPanelBinding = bind.selectionPanel; + selectionPanel = selectionPanelBinding.getRoot(); + + selectionCount = selectionPanelBinding.txtSelectedCount; + btnCopy = selectionPanelBinding.btnCopy; + btnCut = selectionPanelBinding.btnCut; + btnDelete = selectionPanelBinding.btnDelete; + btnPaste = selectionPanelBinding.btnPaste; + btnClose = selectionPanelBinding.btnClose; + btnSelectall = selectionPanelBinding.btnSelectall; + selectionPanelBinding.getRoot().setBackground(ShapeUtil.shapeCustomView(this)); + btnCopy.setOnClickListener(v -> { + List selected = adapter.getSelectedItems(); + if (!selected.isEmpty()) { + pendingClipboard = new ArrayList<>(selected); + isCutOperation = false; + adapter.clearSelection(); + btnPaste.setColorFilter(0xff00ff00); + selectionPanel.setVisibility(View.VISIBLE); + selectionCount.setText("0"); + } }); - btnPaste.setOnClickListener( - v -> { - if (pendingClipboard.isEmpty()) return; - String currentDir = viewModel.getCurrentPath().getValue(); - if (currentDir != null) { - viewModel.pasteFiles( - pendingClipboard, - currentDir, - isCutOperation, - success -> { - if (success) { - pendingClipboard.clear(); - btnPaste.clearColorFilter(); - adapter.clearSelection(); - selectionPanel.setVisibility(View.GONE); - adapter.notifyDataSetChanged(); + btnCut.setOnClickListener(v -> { + List selected = adapter.getSelectedItems(); + if (!selected.isEmpty()) { + pendingClipboard = new ArrayList<>(selected); + isCutOperation = true; + adapter.clearSelection(); + btnPaste.setColorFilter(0xff00ff00); + selectionPanel.setVisibility(View.VISIBLE); + selectionCount.setText("0"); + } + }); + btnDelete.setOnClickListener(v -> { + List selected = adapter.getSelectedItems(); + if (!selected.isEmpty()) { + new MaterialAlertDialogBuilder(this) + .setTitle("Delete") + .setMessage("Delete " + selected.size() + " items?") + .setPositiveButton("Delete", (d, w) -> { + viewModel.deleteFiles(selected); + adapter.clearSelection(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + }); - } else { - Toast.makeText(this, "Paste failed", Toast.LENGTH_SHORT).show(); - } + btnPaste.setOnClickListener(v -> { + if (pendingClipboard.isEmpty()) return; + String currentDir = viewModel.getCurrentPath().getValue(); + if (currentDir != null) { + viewModel.pasteFiles(pendingClipboard, currentDir, isCutOperation, success -> { + if (success) { + pendingClipboard.clear(); + btnPaste.clearColorFilter(); + adapter.clearSelection(); + selectionPanel.setVisibility(View.GONE); + adapter.notifyDataSetChanged(); + } else { + Toast.makeText(this, "Paste failed", Toast.LENGTH_SHORT).show(); + } }); - } + } }); - btnSelectall.setOnClickListener( - v -> { - adapter.selectAll(); - selectionCount.setText( - "Item select " + String.valueOf(adapter.getSelectedItems().size())); - if (selectionPanel.getVisibility() != View.VISIBLE) { - selectionPanel.setVisibility(View.VISIBLE); - } + btnSelectall.setOnClickListener(v -> { + adapter.selectAll(); + selectionCount.setText("Item select " + adapter.getSelectedItems().size()); + if (selectionPanel.getVisibility() != View.VISIBLE) { + selectionPanel.setVisibility(View.VISIBLE); + } }); - btnClose.setOnClickListener( - v -> { - pendingClipboard.clear(); - adapter.clearSelection(); - btnPaste.clearColorFilter(); - selectionPanel.setVisibility(View.GONE); + btnClose.setOnClickListener(v -> { + pendingClipboard.clear(); + adapter.clearSelection(); + btnPaste.clearColorFilter(); + selectionPanel.setVisibility(View.GONE); }); - selectionPanel.setVisibility(View.GONE); - } - - private void setupInsets() { - ViewCompat.setOnApplyWindowInsetsListener( - bind.coordinator, - (view, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - bind.headtop.setPadding(0, systemBars.top, 0, 0); + selectionPanel.setVisibility(View.GONE); + } - bind.fab.post( - () -> { + private void setupInsets() { + ViewCompat.setOnApplyWindowInsetsListener(bind.coordinator, (view, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + bind.toolbar.setPadding(0, systemBars.top, 0, 0); + bind.fab.post(() -> { int fabSpace = bind.fab.getHeight() + 48; bind.rvfiles.setPadding( - bind.rvfiles.getPaddingLeft(), - bind.rvfiles.getPaddingTop(), - bind.rvfiles.getPaddingRight(), - systemBars.bottom + fabSpace); - }); - return insets; + bind.rvfiles.getPaddingLeft(), + bind.rvfiles.getPaddingTop(), + bind.rvfiles.getPaddingRight(), + systemBars.bottom + fabSpace); + }); + return insets; }); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - bind = null; - } - - private void setOnBackPress() { - getOnBackPressedDispatcher() - .addCallback( - this, - new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { + } + + @Override + protected void onDestroy() { + super.onDestroy(); + bind = null; + } + + private void setOnBackPress() { + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { if (viewModel.getCurrentPath().getValue() != null - && !viewModel.getCurrentPath().getValue().equals("/storage/emulated/0")) { - viewModel.navigateUp(); + && !viewModel.getCurrentPath().getValue().equals("/storage/emulated/0")) { + viewModel.navigateUp(); } else { - new MaterialAlertDialogBuilder(FileManagerActivity.this) - .setTitle("Exit") - .setMessage("Exit Ghost IDE?") - .setNegativeButton("Yes", (c, f) -> finishAffinity()) - .setPositiveButton("No", null) - .show(); + new MaterialAlertDialogBuilder(FileManagerActivity.this) + .setTitle("Exit") + .setMessage("Exit Ghost IDE?") + .setNegativeButton("Yes", (c, f) -> finishAffinity()) + .setPositiveButton("No", null) + .show(); } - } - }); - } - - void stepMoreAdapter() { - - adapter.setOnMoreClickListener( - (filemodel, view, pos) -> { - PowerMenu menu = new PowerMenu.Builder(view.getContext()).setIsMaterial(true).build(); - - menu.addItem(new PowerMenuItem(getString(R.string.removed))); - menu.addItem(new PowerMenuItem(getString(R.string.rename))); - // test - menu.addItem(new PowerMenuItem(getString(R.string.removed))); - menu.addItem(new PowerMenuItem(getString(R.string.rename))); - - menu.setMenuColor( - MaterialColors.getColor( - view.getContext(), com.google.android.material.R.attr.colorSurface, 0)); - - menu.setTextColor( - MaterialColors.getColor( - view.getContext(), com.google.android.material.R.attr.colorOnSurface, 0)); - - menu.setShowBackground(false); - menu.setAutoDismiss(true); - menu.setMenuRadius(30f); - menu.setAnimation(MenuAnimation.FADE); + } + }); + } - menu.setOnMenuItemClickListener( - (index, item) -> { + void stepMoreAdapter() { + adapter.setOnMoreClickListener((filemodel, view, pos) -> { + PowerMenu menu = new PowerMenu.Builder(view.getContext()).setIsMaterial(true).build(); + menu.addItem(new PowerMenuItem(getString(R.string.removed))); + menu.addItem(new PowerMenuItem(getString(R.string.rename))); + menu.addItem(new PowerMenuItem(getString(R.string.removed))); + menu.addItem(new PowerMenuItem(getString(R.string.rename))); + menu.setMenuColor(MaterialColors.getColor(view.getContext(), com.google.android.material.R.attr.colorSurface, 0)); + menu.setTextColor(MaterialColors.getColor(view.getContext(), com.google.android.material.R.attr.colorOnSurface, 0)); + menu.setShowBackground(false); + menu.setAutoDismiss(true); + menu.setMenuRadius(30f); + menu.setAnimation(MenuAnimation.FADE); + menu.setOnMenuItemClickListener((index, item) -> { switch (index) { - case 0 -> removedItem(filemodel); - case 1 -> renameItem(filemodel); - case 2 -> creatorFile(filemodel); - case 3 -> creatorFolder(filemodel); + case 0 -> removedItem(filemodel); + case 1 -> renameItem(filemodel); + case 2 -> creatorFile(filemodel); + case 3 -> creatorFolder(filemodel); } - }); - - int[] location = new int[2]; - view.getLocationOnScreen(location); - - int x = location[0]; - int y = location[1]; - - var dm = view.getResources().getDisplayMetrics(); - int screenHeight = dm.heightPixels; + }); + int[] location = new int[2]; + view.getLocationOnScreen(location); + int x = location[0]; + int y = location[1]; + var dm = view.getResources().getDisplayMetrics(); + int screenHeight = dm.heightPixels; + int menuHeight = menu.getContentViewHeight(); + if (menuHeight <= 0) menuHeight = 200; + int spaceAbove = y; + int spaceBelow = screenHeight - (y + view.getHeight()); + if (spaceBelow < menuHeight && spaceAbove > spaceBelow) { + y -= menuHeight; + } else { + y += view.getHeight(); + } + menu.showAtLocation(view, Gravity.TOP | Gravity.START, x, y); + }); + } - int menuHeight = menu.getContentViewHeight(); + void renameItem(FileManagerModel model) { + RenameDialogFragment dialog = RenameDialogFragment.getInstance(model.getName(), + (prefix, extension) -> { + String displayName = TextUtils.isEmpty(extension) ? prefix : prefix + "." + extension; + viewModel.renameFile(model, displayName); + }); + dialog.show(getSupportFragmentManager(), RenameDialogFragment.TAG); + } - if (menuHeight <= 0) { - menuHeight = 200; - } + void removedItem(FileManagerModel model) { + new MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.removed)) + .setMessage(getString(R.string.removedmassges, model.getName() + "?")) + .setPositiveButton("OK", (d, w) -> viewModel.deleteFile(model)) + .setNegativeButton("Cancel", null) + .show(); + } - int spaceAbove = y; - int spaceBelow = screenHeight - (y + view.getHeight()); + void creatorFile(FileManagerModel model) { + TextInputDialogFragment.newInstance("ساخت فایل", "نام فایل", null) + .setCallback(text -> viewModel.createFile(text)) + .show(getSupportFragmentManager(), null); + } - if (spaceBelow < menuHeight && spaceAbove > spaceBelow) { - y -= menuHeight; - } else { - y += view.getHeight(); - } - menu.showAtLocation(view, Gravity.TOP | Gravity.START, x, y); - }); - } - - void renameItem(FileManagerModel model) { - RenameDialogFragment dialog = - RenameDialogFragment.getInstance( - model.getName(), - (prefix, extension) -> { - String displayName; - if (!TextUtils.isEmpty(extension)) { - displayName = prefix + "." + extension; - } else { - displayName = prefix; - } - viewModel.renameFile(model, displayName); - }); - dialog.show(getSupportFragmentManager(), RenameDialogFragment.TAG); - } - - void removedItem(FileManagerModel model) { - - new MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.removed)) - .setMessage(getString(R.string.removedmassges, model.getName() + "?")) - .setPositiveButton( - "OK", - (d, w) -> { - viewModel.deleteFile(model); - }) - .setNegativeButton("Cancel", null) - .show(); - } - - void creatorFile(FileManagerModel model) { - TextInputDialogFragment.newInstance("ساخت فایل", "نام فایل", null) - .setCallback(text -> viewModel.createFile(text)) - .show(getSupportFragmentManager(), null); - } - - void creatorFolder(FileManagerModel model) { - TextInputDialogFragment.newInstance("ساخت پوشه", "پوشه", null) - .setCallback(text -> viewModel.createFolder(text)) - .show(getSupportFragmentManager(), null); - } + void creatorFolder(FileManagerModel model) { + TextInputDialogFragment.newInstance("ساخت پوشه", "پوشه", null) + .setCallback(text -> viewModel.createFolder(text)) + .show(getSupportFragmentManager(), null); + } } diff --git a/app/src/main/java/ir/hanzodev1375/ghostide/fragments/MusicPlayerBottomSheetFragment.java b/app/src/main/java/ir/hanzodev1375/ghostide/fragments/MusicPlayerBottomSheetFragment.java new file mode 100644 index 0000000..31c5857 --- /dev/null +++ b/app/src/main/java/ir/hanzodev1375/ghostide/fragments/MusicPlayerBottomSheetFragment.java @@ -0,0 +1,156 @@ +package ir.hanzodev1375.ghostide.fragments; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import java.io.File; +import ir.hanzodev1375.ghostide.R; +import ir.hanzodev1375.ghostide.utils.MusicPlayerManager; + +public class MusicPlayerBottomSheetFragment extends BottomSheetDialogFragment implements MusicPlayerManager.PlaybackListener { + private TextView songTitle; + private SeekBar seekBar; + private ImageButton btnPlayPause; + private MusicPlayerManager playerManager; + private Handler handler = new Handler(Looper.getMainLooper()); + private Runnable updateSeekBar; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_music_player, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + songTitle = view.findViewById(R.id.songTitle); + seekBar = view.findViewById(R.id.seekBar); + btnPlayPause = view.findViewById(R.id.btnPlayPause); + ImageButton btnPrev = view.findViewById(R.id.btnPrevious); + ImageButton btnNext = view.findViewById(R.id.btnNext); + + playerManager = MusicPlayerManager.getInstance(requireContext()); + playerManager.addPlaybackListener(this); + + String currentPath = playerManager.getCurrentSongPath(); + if (currentPath != null) { + songTitle.setText(new File(currentPath).getName()); + updatePlayPauseButton(); + if (playerManager.isPlaying()) { + startUpdatingSeekBar(); + } + } + + btnPlayPause.setOnClickListener(v -> { + if (playerManager.isPlaying()) { + playerManager.pause(); + } else { + if (playerManager.getCurrentSongPath() != null) { + playerManager.resume(); + } else { + dismiss(); + } + } + updatePlayPauseButton(); + }); + + btnPrev.setOnClickListener(v -> {}); + btnNext.setOnClickListener(v -> {}); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + playerManager.seekTo(progress); + } + } + @Override public void onStartTrackingTouch(SeekBar seekBar) {} + @Override public void onStopTrackingTouch(SeekBar seekBar) {} + }); + } + + @Override + public void onPlaybackStarted(String path) { + songTitle.setText(new File(path).getName()); + updatePlayPauseButton(); + seekBar.setMax(playerManager.getDuration()); + startUpdatingSeekBar(); + } + + @Override + public void onPlaybackPaused() { + updatePlayPauseButton(); + stopUpdatingSeekBar(); + } + + @Override + public void onPlaybackResumed() { + updatePlayPauseButton(); + startUpdatingSeekBar(); + } + + @Override + public void onPlaybackStopped() { + updatePlayPauseButton(); + stopUpdatingSeekBar(); + dismiss(); + } + + @Override + public void onPlaybackCompleted() { + updatePlayPauseButton(); + stopUpdatingSeekBar(); + dismiss(); + } + + @Override + public void onPositionChanged(int position) { + seekBar.setProgress(position); + } + + private void updatePlayPauseButton() { + if (playerManager.isPlaying()) { + btnPlayPause.setImageResource(android.R.drawable.ic_media_pause); + } else { + btnPlayPause.setImageResource(android.R.drawable.ic_media_play); + } + } + + private void startUpdatingSeekBar() { + if (updateSeekBar != null) stopUpdatingSeekBar(); + updateSeekBar = new Runnable() { + @Override + public void run() { + if (playerManager.isPlaying()) { + seekBar.setProgress(playerManager.getCurrentPosition()); + handler.postDelayed(this, 500); + } + } + }; + handler.post(updateSeekBar); + } + + private void stopUpdatingSeekBar() { + if (updateSeekBar != null) { + handler.removeCallbacks(updateSeekBar); + updateSeekBar = null; + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + playerManager.removePlaybackListener(this); + stopUpdatingSeekBar(); + } +} diff --git a/app/src/main/java/ir/hanzodev1375/ghostide/utils/MusicPlayerManager.java b/app/src/main/java/ir/hanzodev1375/ghostide/utils/MusicPlayerManager.java new file mode 100644 index 0000000..2eb4d24 --- /dev/null +++ b/app/src/main/java/ir/hanzodev1375/ghostide/utils/MusicPlayerManager.java @@ -0,0 +1,184 @@ +package ir.hanzodev1375.ghostide.utils; + +import android.content.Context; +import android.media.MediaPlayer; +import android.os.Handler; +import android.os.Looper; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MusicPlayerManager { + private static MusicPlayerManager instance; + private Context context; + private MediaPlayer mediaPlayer; + private String currentSongPath; + private boolean isPlaying; + private int currentPosition; + private List listeners = new ArrayList<>(); + + private MusicPlayerManager(Context context) { + this.context = context.getApplicationContext(); + } + + public static synchronized MusicPlayerManager getInstance(Context context) { + if (instance == null) { + instance = new MusicPlayerManager(context); + } + return instance; + } + + public void play(String path) { + try { + if (mediaPlayer != null) { + mediaPlayer.release(); + } + mediaPlayer = new MediaPlayer(); + mediaPlayer.setDataSource(path); + mediaPlayer.prepare(); + mediaPlayer.start(); + currentSongPath = path; + isPlaying = true; + notifyPlaybackStarted(); + setupCompletionListener(); + setupPositionUpdater(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void pause() { + if (mediaPlayer != null && mediaPlayer.isPlaying()) { + mediaPlayer.pause(); + isPlaying = false; + notifyPlaybackPaused(); + } + } + + public void resume() { + if (mediaPlayer != null && !mediaPlayer.isPlaying()) { + mediaPlayer.start(); + isPlaying = true; + notifyPlaybackResumed(); + } + } + + public void stop() { + if (mediaPlayer != null) { + mediaPlayer.stop(); + mediaPlayer.release(); + mediaPlayer = null; + isPlaying = false; + currentSongPath = null; + notifyPlaybackStopped(); + } + } + + public boolean isPlaying() { + return isPlaying; + } + + public String getCurrentSongPath() { + return currentSongPath; + } + + public int getDuration() { + if (mediaPlayer != null) { + return mediaPlayer.getDuration(); + } + return 0; + } + + public int getCurrentPosition() { + if (mediaPlayer != null && isPlaying) { + currentPosition = mediaPlayer.getCurrentPosition(); + } + return currentPosition; + } + + public void seekTo(int position) { + if (mediaPlayer != null) { + mediaPlayer.seekTo(position); + currentPosition = position; + } + } + + private void setupCompletionListener() { + if (mediaPlayer != null) { + mediaPlayer.setOnCompletionListener(mp -> { + isPlaying = false; + currentSongPath = null; + notifyPlaybackCompleted(); + }); + } + } + + private void setupPositionUpdater() { + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (mediaPlayer != null && isPlaying) { + currentPosition = mediaPlayer.getCurrentPosition(); + notifyPositionChanged(currentPosition); + handler.postDelayed(this, 500); + } + } + }, 500); + } + + public void addPlaybackListener(PlaybackListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + public void removePlaybackListener(PlaybackListener listener) { + listeners.remove(listener); + } + + private void notifyPlaybackStarted() { + for (PlaybackListener l : listeners) { + l.onPlaybackStarted(currentSongPath); + } + } + + private void notifyPlaybackPaused() { + for (PlaybackListener l : listeners) { + l.onPlaybackPaused(); + } + } + + private void notifyPlaybackResumed() { + for (PlaybackListener l : listeners) { + l.onPlaybackResumed(); + } + } + + private void notifyPlaybackStopped() { + for (PlaybackListener l : listeners) { + l.onPlaybackStopped(); + } + } + + private void notifyPlaybackCompleted() { + for (PlaybackListener l : listeners) { + l.onPlaybackCompleted(); + } + } + + private void notifyPositionChanged(int position) { + for (PlaybackListener l : listeners) { + l.onPositionChanged(position); + } + } + + public interface PlaybackListener { + void onPlaybackStarted(String path); + void onPlaybackPaused(); + void onPlaybackResumed(); + void onPlaybackStopped(); + void onPlaybackCompleted(); + void onPositionChanged(int position); + } +} diff --git a/app/src/main/res/drawable/bottom_sheet_background.xml b/app/src/main/res/drawable/bottom_sheet_background.xml new file mode 100644 index 0000000..79b7c05 --- /dev/null +++ b/app/src/main/res/drawable/bottom_sheet_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_music.png b/app/src/main/res/drawable/ic_music.png new file mode 100644 index 0000000000000000000000000000000000000000..38f4d860dff7890edda3e3c7a5de9a834f0b002c GIT binary patch literal 631 zcmV--0*L*IP)Px#1am@3R0s$N2z&@+hyVZrAxT6*RA_H)GqPwPgI)wU?S=`K> zbI<9n?&=VNKZ4v3KfBi>9zKh7Nh`bY#Wn;{@ShIrxD!_AK zgZ!)m&wxr<=AQuj0me0Zz{6G=?*1Ul-UAz%ua z1y<_4Ha-G_MVkL=8F~cF19x(0jRLQLV~gG%Fc8~(x8um~0258<-Uq%|bUy&sV+OFT z`6Zwyf_5*kY|)>K;r-v;#%8`f%7EI8c1MT@%?ej&yjAYRIP#WRy_V&)OEdcfwN2_g zN5H*^zV*CG8ZYdp%?Ropc0QtC*GzrAt%VAWDxni{+$uu>#%SCphF$$_9<4{JUTwt# zm@#HwY7cxqFK74yR8{>Z)KmaVs-AK+`T~rrdL!6Qe%4ieH@5=tQ?0{do>AZTn zT)n^XW&wh0+h|Qtc`k2YVuwq7wKi? z(w~78plnirvPl8TCIu)PUx34wxwx3Wt}lE6whJ&4iqnfP!0Q5xgtD0X7eFVlMMeVj zv$6?vI@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_filemanager.xml b/app/src/main/res/layout/activity_filemanager.xml index caf61bb..8b9b09d 100644 --- a/app/src/main/res/layout/activity_filemanager.xml +++ b/app/src/main/res/layout/activity_filemanager.xml @@ -1,71 +1,69 @@ - + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/coordinator" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - - - - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + + android:id="@+id/headline" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:layout_marginTop="-20dp" + android:background="#000000" + android:clipToOutline="true" + android:elevation="8dp" + android:orientation="vertical" + android:padding="8dp"> + android:id="@+id/navmodel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="5dp" + android:layout_marginRight="5dp" /> + android:id="@+id/loadingprogass" + style="?android:progressBarStyleHorizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:indeterminate="false" + android:padding="8dp" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="5dp"> + android:id="@+id/rvfiles" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" /> + android:id="@+id/emptystates" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="gone" /> @@ -74,24 +72,21 @@ + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:layout_margin="16dp" + android:src="@drawable/add" /> - - \ No newline at end of file + android:id="@+id/selectionPanel" + layout="@layout/selection_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + app:layout_anchor="@id/fab" + app:layout_anchorGravity="bottom" /> + + diff --git a/app/src/main/res/layout/fragment_music_player.xml b/app/src/main/res/layout/fragment_music_player.xml new file mode 100644 index 0000000..4f2754c --- /dev/null +++ b/app/src/main/res/layout/fragment_music_player.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_filemanager.xml b/app/src/main/res/menu/menu_filemanager.xml new file mode 100644 index 0000000..9641116 --- /dev/null +++ b/app/src/main/res/menu/menu_filemanager.xml @@ -0,0 +1,9 @@ + + + +