From b16396b101e8717d9f1e03ff0ed4db28d104fcf1 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Mon, 14 Oct 2024 13:26:13 +0300 Subject: [PATCH 1/2] Feat[launcher]: add "Open game directory" button - Also refactors the file sending mechanism. --- .../net/kdt/pojavlaunch/MainActivity.java | 42 ++++---------- .../main/java/net/kdt/pojavlaunch/Tools.java | 58 ++++++++++++++++--- .../fragments/MainMenuFragment.java | 19 ++++++ .../res/layout-land/fragment_launcher.xml | 56 +++++++++--------- .../src/main/res/layout/fragment_launcher.xml | 54 +++++++++-------- .../src/main/res/values-land/styles.xml | 7 +++ .../src/main/res/values/strings.xml | 1 + .../src/main/res/values/styles.xml | 13 +++++ 8 files changed, 160 insertions(+), 90 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index 4909197dbe..afa1172199 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -510,38 +510,20 @@ public void onStopTrackingTouch(SeekBar seekBar) {} b.show(); } - private static void setUri(Context context, String input, Intent intent) { - if(input.startsWith("file:")) { - int truncLength = 5; - if(input.startsWith("file://")) truncLength = 7; - input = input.substring(truncLength); - Log.i("MainActivity", input); - boolean isDirectory = new File(input).isDirectory(); - if(isDirectory) { - intent.setType(DocumentsContract.Document.MIME_TYPE_DIR); - }else{ - String type = null; - String extension = MimeTypeMap.getFileExtensionFromUrl(input); - if(extension != null) type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if(type == null) type = "*/*"; - intent.setType(type); - } - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setData(DocumentsContract.buildDocumentUri( - context.getString(R.string.storageProviderAuthorities), input - )); - return; - } - intent.setDataAndType(Uri.parse(input), "*/*"); - } - public static void openLink(String link) { Context ctx = touchpad.getContext(); // no more better way to obtain a context statically ((Activity)ctx).runOnUiThread(() -> { try { - Intent intent = new Intent(Intent.ACTION_VIEW); - setUri(ctx, link, intent); - ctx.startActivity(intent); + if(link.startsWith("file:")) { + int truncLength = 5; + if(link.startsWith("file://")) truncLength = 7; + String path = link.substring(truncLength); + Tools.openPath(ctx, new File(path), false); + }else { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(link), "*/*"); + ctx.startActivity(intent); + } } catch (Throwable th) { Tools.showError(ctx, th); } @@ -552,9 +534,7 @@ public static void openPath(String path) { Context ctx = touchpad.getContext(); // no more better way to obtain a context statically ((Activity)ctx).runOnUiThread(() -> { try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(DocumentsContract.buildDocumentUri(ctx.getString(R.string.storageProviderAuthorities), path), "*/*"); - ctx.startActivity(intent); + Tools.openPath(ctx, new File(path), false); } catch (Throwable th) { Tools.showError(ctx, th); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index 0268bce8c5..431e8c1fbe 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -72,6 +72,7 @@ import org.apache.commons.io.IOUtils; import org.lwjgl.glfw.CallbackBridge; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -82,6 +83,7 @@ import java.io.StringWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Field; +import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; @@ -1108,17 +1110,55 @@ public static void runOnUiThread(Runnable runnable) { /** Triggers the share intent chooser, with the latestlog file attached to it */ public static void shareLog(Context context){ - Uri contentUri = DocumentsContract.buildDocumentUri(context.getString(R.string.storageProviderAuthorities), Tools.DIR_GAME_HOME + "/latestlog.txt"); + openPath(context, new File(Tools.DIR_GAME_HOME, "latestlog.txt"), true); + } - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - shareIntent.setType("text/plain"); + /** + * Determine the MIME type of a File. + * @param file The file to determine the type of + * @return the type, or the default value *slash* if cannot be determined + */ + public static String getMimeType(File file) { + if(file.isDirectory()) return DocumentsContract.Document.MIME_TYPE_DIR; + String mimeType = null; + try (FileInputStream fileInputStream = new FileInputStream(file)){ + // Theoretically we don't even need the buffer since we don't care about the + // contents of the file after the guess, but mark-supported streams + // are a requirement of URLConnection.guessContentTypeFromStream() + try(BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) { + mimeType = URLConnection.guessContentTypeFromStream(bufferedInputStream); + } + }catch (IOException e) { + Log.w("FileMimeType", "Failed to determine MIME type by stream", e); + } + if(mimeType != null) return mimeType; + mimeType = URLConnection.guessContentTypeFromName(file.getName()); + if(mimeType != null) return mimeType; + return "*/*"; + } - Intent sendIntent = Intent.createChooser(shareIntent, "latestlog.txt"); - context.startActivity(sendIntent); + /** + * Open the path specified by a File in a file explorer or in a relevant application. + * @param context the current Context + * @param file the File to open + * @param share whether to open a "Share" or an "Open" dialog. + */ + public static void openPath(Context context, File file, boolean share) { + Uri contentUri = DocumentsContract.buildDocumentUri(context.getString(R.string.storageProviderAuthorities), file.getAbsolutePath()); + String mimeType = getMimeType(file); + Intent intent = new Intent(); + if(share) { + intent.setAction(Intent.ACTION_SEND); + intent.setType(getMimeType(file)); + intent.putExtra(Intent.EXTRA_STREAM, contentUri); + }else { + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(contentUri, mimeType); + } + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent chooserIntent = Intent.createChooser(intent, file.getName()); + context.startActivity(chooserIntent); } /** Mesure the textview height, given its current parameters */ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java index 79a87bccd5..c760ecfa88 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java @@ -1,5 +1,6 @@ package net.kdt.pojavlaunch.fragments; +import static net.kdt.pojavlaunch.Tools.openPath; import static net.kdt.pojavlaunch.Tools.shareLog; import android.content.Intent; @@ -20,7 +21,12 @@ import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.extra.ExtraConstants; import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; + +import java.io.File; public class MainMenuFragment extends Fragment { public static final String TAG = "MainMenuFragment"; @@ -38,6 +44,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Button mCustomControlButton = view.findViewById(R.id.custom_control_button); Button mInstallJarButton = view.findViewById(R.id.install_jar_button); Button mShareLogsButton = view.findViewById(R.id.share_logs_button); + Button mOpenDirectoryButton = view.findViewById(R.id.open_files_button); ImageButton mEditProfileButton = view.findViewById(R.id.edit_profile_button); Button mPlayButton = view.findViewById(R.id.play_button); @@ -57,12 +64,24 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext())); + mOpenDirectoryButton.setOnClickListener((v)-> openPath(v.getContext(), getCurrentProfileDirectory(), false)); + + mNewsButton.setOnLongClickListener((v)->{ Tools.swapFragment(requireActivity(), GamepadMapperFragment.class, GamepadMapperFragment.TAG, null); return true; }); } + private File getCurrentProfileDirectory() { + String currentProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE, null); + if(!Tools.isValidString(currentProfile)) return new File(Tools.DIR_GAME_NEW); + LauncherProfiles.load(); + MinecraftProfile profileObject = LauncherProfiles.mainProfileJson.profiles.get(currentProfile); + if(profileObject == null) return new File(Tools.DIR_GAME_NEW); + return Tools.getGameDirPath(profileObject); + } + @Override public void onResume() { super.onResume(); diff --git a/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml b/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml index 7881e87478..3735b4724e 100644 --- a/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml @@ -30,11 +30,8 @@ app:layout_constraintGuide_percent="0.5"/> + app:layout_constraintTop_toBottomOf="@id/custom_control_button" + tools:layout_editor_absoluteX="0dp" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/install_jar_button" + app:layout_constraintEnd_toStartOf="@id/files_divider"/> + + + diff --git a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml index e5c9d03017..7cb6d9142f 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml @@ -26,10 +26,8 @@ app:layout_constraintGuide_percent="0.5"/> + app:layout_constraintTop_toBottomOf="@id/news_button" /> + app:layout_constraintTop_toBottomOf="@id/custom_control_button" /> + app:layout_constraintEnd_toStartOf="@id/files_divider"/> + + + + diff --git a/app_pojavlauncher/src/main/res/values-land/styles.xml b/app_pojavlauncher/src/main/res/values-land/styles.xml index 82b6ade30e..afdf92305b 100644 --- a/app_pojavlauncher/src/main/res/values-land/styles.xml +++ b/app_pojavlauncher/src/main/res/values-land/styles.xml @@ -12,4 +12,11 @@ @dimen/_14ssp + + + diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 67d1675d4b..95ee9a0a05 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -415,5 +415,6 @@ Change controller key bindings Allows you to modify the keyboard keys bound to each controller button Discord + Open game directory https://discord.gg/pojavlauncher-724163890803638273 diff --git a/app_pojavlauncher/src/main/res/values/styles.xml b/app_pojavlauncher/src/main/res/values/styles.xml index 8171c88146..a9b2199afc 100644 --- a/app_pojavlauncher/src/main/res/values/styles.xml +++ b/app_pojavlauncher/src/main/res/values/styles.xml @@ -13,6 +13,19 @@ + + + + +