From 6980dfdbfc09f91305d367f6904ec3bc9efd22fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCleyman=20Yasir=20KULA?= Date: Sun, 29 Nov 2020 14:46:55 +0300 Subject: [PATCH] - Changed folderMode parameter to pickMode that supports picking files and folders simultaneously - Added optional initialFilename parameter to prefill the filename input field - Files and folders are now sorted by their names (they weren't automatically sorted on some platforms) - Made AllFilesFilterText, FoldersFilterText and PickFolderQuickLinkText properties public static so that these labels can be localized or customized - Write External Storage permission is now added automatically on Android (no manual setup is needed) --- .../AAR Source (Android)/AndroidManifest.xml | 5 + .../java/com/yasirkula/unity/FileBrowser.java | 0 .../FileBrowserDirectoryPickerFragment.java | 0 .../unity/FileBrowserDirectoryReceiver.java | 0 .../unity/FileBrowserPermissionFragment.java | 0 .../unity/FileBrowserPermissionReceiver.java | 0 .../yasirkula/unity/FileBrowserSAFEntry.java | 0 .github/README.md | 39 ++-- .../Android/SimpleFileBrowser.aar | Bin 0 -> 14372 bytes ...er.jar.meta => SimpleFileBrowser.aar.meta} | 4 +- .../Android/SimpleFileBrowser.jar | Bin 15000 -> 0 bytes Plugins/SimpleFileBrowser/Editor.meta | 9 + .../Editor/SFBPostProcessBuild.cs | 17 ++ .../Editor/SFBPostProcessBuild.cs.meta | 12 + .../Editor/SimpleFileBrowser.Editor.asmdef | 15 ++ .../SimpleFileBrowser.Editor.asmdef.meta | 7 + Plugins/SimpleFileBrowser/README.txt | 27 ++- .../SimpleFileBrowser/Scripts/FileBrowser.cs | 220 +++++++++--------- package.json | 2 +- 19 files changed, 218 insertions(+), 139 deletions(-) create mode 100644 .github/AAR Source (Android)/AndroidManifest.xml rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowser.java (100%) rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowserDirectoryPickerFragment.java (100%) rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowserDirectoryReceiver.java (100%) rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowserPermissionFragment.java (100%) rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowserPermissionReceiver.java (100%) rename .github/{JAR Source (Android) => AAR Source (Android)}/java/com/yasirkula/unity/FileBrowserSAFEntry.java (100%) create mode 100644 Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar rename Plugins/SimpleFileBrowser/Android/{SimpleFileBrowser.jar.meta => SimpleFileBrowser.aar.meta} (89%) delete mode 100644 Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar create mode 100644 Plugins/SimpleFileBrowser/Editor.meta create mode 100644 Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs create mode 100644 Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs.meta create mode 100644 Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef create mode 100644 Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef.meta diff --git a/.github/AAR Source (Android)/AndroidManifest.xml b/.github/AAR Source (Android)/AndroidManifest.xml new file mode 100644 index 0000000..6af2e90 --- /dev/null +++ b/.github/AAR Source (Android)/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowser.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowser.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowser.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowser.java diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryPickerFragment.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryPickerFragment.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryPickerFragment.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryPickerFragment.java diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryReceiver.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryReceiver.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryReceiver.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserDirectoryReceiver.java diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionFragment.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionFragment.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionFragment.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionFragment.java diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionReceiver.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionReceiver.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionReceiver.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserPermissionReceiver.java diff --git a/.github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserSAFEntry.java b/.github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserSAFEntry.java similarity index 100% rename from .github/JAR Source (Android)/java/com/yasirkula/unity/FileBrowserSAFEntry.java rename to .github/AAR Source (Android)/java/com/yasirkula/unity/FileBrowserSAFEntry.java diff --git a/.github/README.md b/.github/README.md index cd7ebbd..7fbfd7b 100644 --- a/.github/README.md +++ b/.github/README.md @@ -20,7 +20,7 @@ - Supports runtime permissions on Android M+ and *Storage Access Framework* on Android Q+ - Optimized using a recycled list view (makes *Instantiate* calls sparingly) -**NOTE:** Universal Windows Platform (UWP) is not supported! +**NOTE:** *Universal Windows Platform (UWP)* and *WebGL* platforms aren't supported! ## INSTALLATION @@ -40,18 +40,16 @@ There are 5 ways to install this plugin: If your project uses ProGuard, try adding the following line to ProGuard filters: `-keep class com.yasirkula.unity.* { *; }` -- **File browser doesn't show any files on Android** +- **File browser doesn't show any files on Android 10+** -Make sure that you've set the **Write Permission** to **External (SDCard)** in *Player Settings*. On Android 10+, file browser uses *Storage Access Framework* and users must click the *Pick Folder* button first. +File browser uses *Storage Access Framework* on these Android versions and users must first click the *Pick Folder* button in the quick links section -- **RequestPermission returns Permission.Denied on Android even though I've set "Write Permission" to "External (SDCard)"** +- **RequestPermission returns Permission.Denied on Android** Declare the `WRITE_EXTERNAL_STORAGE` permission manually in your [**Plugins/Android/AndroidManifest.xml** file](https://answers.unity.com/questions/982710/where-is-the-manifest-file-in-unity.html) with the `tools:node="replace"` attribute as follows: `` (you'll need to add the `xmlns:tools="http://schemas.android.com/tools"` attribute to the `` element). ## HOW TO -*for Android*: set **Write Permission** to **External (SDCard)** in **Player Settings** - **NOTE:** On *Android Q (10)* or later, it is impossible to work with *File* APIs. On these devices, SimpleFileBrowser uses *Storage Access Framework (SAF)* to browse the files. However, paths returned by SAF are not File API compatible. To simulate the behaviour of the File API on all devices (including SAF), you can check out the **FileBrowserHelpers** functions. For reference, here is an example SAF path: `content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3APictures` First, add `using SimpleFileBrowser;` to your script. @@ -59,8 +57,8 @@ First, add `using SimpleFileBrowser;` to your script. The file browser can be shown either as a **save dialog** or a **load dialog**. In load mode, the returned path(s) always lead to existing files or folders. In save mode, the returned path(s) can point to non-existing files, as well. You can use the following functions to show the file browser: ```csharp -public static bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Save", string saveButtonText = "Save" ); -public static bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Load", string loadButtonText = "Select" ); +public static bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ); +public static bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ); public delegate void OnSuccess( string[] paths ); public delegate void OnCancel(); @@ -68,13 +66,13 @@ public delegate void OnCancel(); There can only be one dialog active at a time. These functions will return *true* if the dialog is shown successfully (if no other dialog is active), *false* otherwise. You can query the **FileBrowser.IsOpen** property to see if there is an active dialog at the moment. -If user presses the *Cancel* button, **onCancel** callback is called. Otherwise, **onSuccess** callback is called with the paths of the selected files/folders as parameter. When **folderMode** is set to *true*, the file browser will show only folders and the user will pick folders instead of files. Setting **allowMultiSelection** to *true* will allow picking multiple files/folders. +If user presses the *Cancel* button, **onCancel** callback is called. Otherwise, **onSuccess** callback is called with the paths of the selected files/folders as parameter. **pickMode** can be *Files*, *Folders* or *FilesAndFolders*. Setting **allowMultiSelection** to *true* will allow picking multiple files/folders. There are also coroutine variants of these functions that will yield while the dialog is active: ```csharp -public static IEnumerator WaitForSaveDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Save", string saveButtonText = "Save" ); -public static IEnumerator WaitForLoadDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Load", string loadButtonText = "Select" ); +public static IEnumerator WaitForSaveDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ); +public static IEnumerator WaitForLoadDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ); ``` After the dialog is closed, you can check the **FileBrowser.Success** property to see whether the user has selected some files/folders or cancelled the operation and if FileBrowser.Success is set to *true*, you can use the **FileBrowser.Result** property to get the paths of the selected files/folders. @@ -205,17 +203,19 @@ public class FileBrowserTest : MonoBehaviour // onSuccess event: not registered (which means this dialog is pretty useless) // onCancel event: not registered // Save file/folder: file, Allow multiple selection: false - // Initial path: "C:\", Title: "Save As", submit button text: "Save" - // FileBrowser.ShowSaveDialog( null, null, false, false, "C:\\", "Save As", "Save" ); + // Initial path: "C:\", Initial filename: "Screenshot.png" + // Title: "Save As", Submit button text: "Save" + // FileBrowser.ShowSaveDialog( null, null, FileBrowser.PickMode.Files, false, "C:\\", "Screenshot.png", "Save As", "Save" ); // Show a select folder dialog // onSuccess event: print the selected folder's path // onCancel event: print "Canceled" // Load file/folder: folder, Allow multiple selection: false - // Initial path: default (Documents), Title: "Select Folder", submit button text: "Select" + // Initial path: default (Documents), Initial filename: empty + // Title: "Select Folder", Submit button text: "Select" // FileBrowser.ShowLoadDialog( ( paths ) => { Debug.Log( "Selected: " + paths[0] ); }, - // () => { Debug.Log( "Canceled" ); }, - // true, false, null, "Select Folder", "Select" ); + // () => { Debug.Log( "Canceled" ); }, + // FileBrowser.PickMode.Folders, false, null, null, "Select Folder", "Select" ); // Coroutine example StartCoroutine( ShowLoadDialogCoroutine() ); @@ -224,9 +224,10 @@ public class FileBrowserTest : MonoBehaviour IEnumerator ShowLoadDialogCoroutine() { // Show a load file dialog and wait for a response from user - // Load file/folder: file, Allow multiple selection: true - // Initial path: default (Documents), Title: "Load File", submit button text: "Load" - yield return FileBrowser.WaitForLoadDialog( false, true, null, "Load File", "Load" ); + // Load file/folder: both, Allow multiple selection: true + // Initial path: default (Documents), Initial filename: empty + // Title: "Load File", Submit button text: "Load" + yield return FileBrowser.WaitForLoadDialog( FileBrowser.PickMode.FilesAndFolders, true, null, null, "Load Files and Folders", "Load" ); // Dialog is closed // Print whether the user has selected some files/folders or cancelled the operation (FileBrowser.Success) diff --git a/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar b/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar new file mode 100644 index 0000000000000000000000000000000000000000..2a86e2ac423bc7c387ebf0371d068a36d2d60378 GIT binary patch literal 14372 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 E0DF7@>;M1& literal 0 HcmV?d00001 diff --git a/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar.meta b/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar.meta similarity index 89% rename from Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar.meta rename to Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar.meta index 3ba7fb8..f3175c3 100644 --- a/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar.meta +++ b/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: 912e8e35fbd849844b7aa2a6a00db170 -timeCreated: 1520240264 +guid: cae0a78f915b13748ba09fd56bafb4c8 +timeCreated: 1606638456 licenseType: Free PluginImporter: serializedVersion: 2 diff --git a/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar b/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar deleted file mode 100644 index 33bcb31bb6a4833749832e004b363d507f30c80f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15000 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 diff --git a/Plugins/SimpleFileBrowser/Editor.meta b/Plugins/SimpleFileBrowser/Editor.meta new file mode 100644 index 0000000..51cd287 --- /dev/null +++ b/Plugins/SimpleFileBrowser/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b2721c50408d790489bb17f9babc1b86 +folderAsset: yes +timeCreated: 1606639083 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs b/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs new file mode 100644 index 0000000..66ce3a1 --- /dev/null +++ b/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs @@ -0,0 +1,17 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +public class SFBPostProcessBuild +{ + [InitializeOnLoadMethod] + public static void ValidatePlugin() + { + string jarPath = "Assets/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar"; + if( File.Exists( jarPath ) ) + { + Debug.Log( "Deleting obsolete " + jarPath ); + AssetDatabase.DeleteAsset( jarPath ); + } + } +} \ No newline at end of file diff --git a/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs.meta b/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs.meta new file mode 100644 index 0000000..81eecf3 --- /dev/null +++ b/Plugins/SimpleFileBrowser/Editor/SFBPostProcessBuild.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ab8be3c1e48785d408ea8b2369c99ccd +timeCreated: 1521452119 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef b/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef new file mode 100644 index 0000000..e9dbcb4 --- /dev/null +++ b/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef @@ -0,0 +1,15 @@ +{ + "name": "SimpleFileBrowser.Editor", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef.meta b/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef.meta new file mode 100644 index 0000000..44671d3 --- /dev/null +++ b/Plugins/SimpleFileBrowser/Editor/SimpleFileBrowser.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9c949a75d90a5fd40b5ac887c3c3a3b0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/SimpleFileBrowser/README.txt b/Plugins/SimpleFileBrowser/README.txt index d6d28a8..26f3887 100644 --- a/Plugins/SimpleFileBrowser/README.txt +++ b/Plugins/SimpleFileBrowser/README.txt @@ -6,12 +6,24 @@ E-mail: yasirkula@gmail.com 1. ABOUT This plugin helps you show save/load dialogs during gameplay with its uGUI based file browser. -2. HOW TO -for Android: set Write Permission to External (SDCard) in Player Settings +2. HOW TO The file browser can be shown either as a save dialog or a load dialog. In load mode, the returned path(s) always lead to existing files or folders. In save mode, the returned path(s) can point to non-existing files, as well. -3. SCRIPTING API + +3. FAQ +- Can't show the file browser on Android, it says "java.lang.ClassNotFoundException: com.yasirkula.unity.FileBrowserPermissionReceiver" in Logcat +If your project uses ProGuard, try adding the following line to ProGuard filters: -keep class com.yasirkula.unity.* { *; } + +- File browser doesn't show any files on Android 10+ +File browser uses Storage Access Framework on these Android versions and users must first click the "Pick Folder" button in the quick links section + +- RequestPermission returns Permission.Denied on Android +Declare the WRITE_EXTERNAL_STORAGE permission manually in your Plugins/Android/AndroidManifest.xml file as follows: +You'll need to add the following attribute to the '' element: xmlns:tools="http://schemas.android.com/tools" + + +4. SCRIPTING API Please see the online documentation for a more in-depth documentation of the Scripting API: https://github.com/yasirkula/UnitySimpleFileBrowser NOTE: On Android Q (10) or later, it is impossible to work with File APIs. On these devices, SimpleFileBrowser uses Storage Access Framework (SAF) to browse the files. However, paths returned by SAF are not File API compatible. To simulate the behaviour of the File API on all devices (including SAF), you can check out the FileBrowserHelpers functions. For reference, here is an example SAF path: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3APictures @@ -20,16 +32,17 @@ NOTE: On Android Q (10) or later, it is impossible to work with File APIs. On th using SimpleFileBrowser; public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 }; +public enum PickMode { Files = 0, Folders = 1, FilesAndFolders = 2 }; public delegate void OnSuccess( string path ); public delegate void OnCancel(); // Showing dialog -bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Save", string saveButtonText = "Save" ); -bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Load", string loadButtonText = "Select" ); +bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ); +bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ); -IEnumerator WaitForSaveDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Save", string saveButtonText = "Save" ); -IEnumerator WaitForLoadDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, string title = "Load", string loadButtonText = "Select" ); +IEnumerator WaitForSaveDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ); +IEnumerator WaitForLoadDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ); // Force closing an open dialog void HideDialog( bool invokeCancelCallback = false ); diff --git a/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs b/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs index 58ecbaa..7455d9b 100644 --- a/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs +++ b/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs @@ -12,6 +12,7 @@ namespace SimpleFileBrowser public class FileBrowser : MonoBehaviour, IListViewAdapter { public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 }; + public enum PickMode { Files = 0, Folders = 1, FilesAndFolders = 2 }; #region Structs #pragma warning disable 0649 @@ -99,19 +100,15 @@ public override string ToString() #endregion #region Constants - private const string ALL_FILES_FILTER_TEXT = "All Files (.*)"; - private const string FOLDERS_FILTER_TEXT = "Folders"; - private string DEFAULT_PATH; - private const int FILENAME_INPUT_FIELD_MAX_FILE_COUNT = 7; - -#if !UNITY_EDITOR && UNITY_ANDROID - private const string SAF_PICK_FOLDER_QUICK_LINK_TEXT = "Pick Folder"; private const string SAF_PICK_FOLDER_QUICK_LINK_PATH = "SAF_PICK_FOLDER"; -#endif #endregion #region Static Variables + public static string AllFilesFilterText = "All Files (.*)"; + public static string FoldersFilterText = "Folders"; + public static string PickFolderQuickLinkText = "Pick Folder"; + public static bool IsOpen { get; private set; } public static bool Success { get; private set; } @@ -172,6 +169,9 @@ private static FileBrowser Instance [SerializeField] private float quickLinksMaxWidthPercentage = 0.4f; + [SerializeField] + private bool sortFilesByName = true; + [SerializeField] private string[] excludeExtensions; @@ -345,6 +345,8 @@ private static FileBrowser Instance private bool showAllFilesFilter = true; + private string defaultInitialPath; + private int currentPathIndex = -1; private readonly List pathsFollowed = new List(); @@ -413,7 +415,7 @@ private string CurrentPath filesScrollRect.verticalNormalizedPosition = 1; filenameImage.color = Color.white; - if( m_folderSelectMode ) + if( m_pickerMode != PickMode.Files ) { filenameInputField.text = string.Empty; filenameInputField.interactable = true; @@ -428,10 +430,7 @@ private string CurrentPath private string m_searchString = string.Empty; private string SearchString { - get - { - return m_searchString; - } + get { return m_searchString; } set { if( m_searchString != value ) @@ -451,36 +450,30 @@ private bool AcceptNonExistingFilename set { m_acceptNonExistingFilename = value; } } - private bool m_folderSelectMode = false; - private bool FolderSelectMode + private PickMode m_pickerMode = PickMode.Files; + private PickMode PickerMode { - get - { - return m_folderSelectMode; - } + get { return m_pickerMode; } set { - if( m_folderSelectMode != value ) - { - m_folderSelectMode = value; + m_pickerMode = value; - if( m_folderSelectMode ) - { - filtersDropdown.options[0].text = FOLDERS_FILTER_TEXT; - filtersDropdown.value = 0; - filtersDropdown.RefreshShownValue(); - filtersDropdown.interactable = false; - } - else - { - filtersDropdown.options[0].text = filters[0].ToString(); - filtersDropdown.interactable = true; - } - - Text placeholder = filenameInputField.placeholder as Text; - if( placeholder ) - placeholder.gameObject.SetActive( !m_folderSelectMode ); + if( m_pickerMode == PickMode.Folders ) + { + filtersDropdown.options[0].text = FoldersFilterText; + filtersDropdown.value = 0; + filtersDropdown.RefreshShownValue(); + filtersDropdown.interactable = false; } + else + { + filtersDropdown.options[0].text = filters[0].ToString(); + filtersDropdown.interactable = true; + } + + Text placeholder = filenameInputField.placeholder as Text; + if( placeholder ) + placeholder.gameObject.SetActive( m_pickerMode != PickMode.Folders ); } } @@ -551,9 +544,9 @@ private void Awake() nullPointerEventData = new PointerEventData( null ); #if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS || UNITY_WSA || UNITY_WSA_10_0 ) - DEFAULT_PATH = Application.persistentDataPath; + defaultInitialPath = Application.persistentDataPath; #else - DEFAULT_PATH = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ); + defaultInitialPath = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ); #endif #if !UNITY_EDITOR && UNITY_ANDROID @@ -579,7 +572,7 @@ private void Awake() filenameInputField.onValidateInput += OnValidateFilenameInput; filenameInputField.onValueChanged.AddListener( OnFilenameInputChanged ); - allFilesFilter = new Filter( ALL_FILES_FILTER_TEXT ); + allFilesFilter = new Filter( AllFilesFilterText ); filters.Add( allFilesFilter ); invalidFilenameChars = new HashSet( Path.GetInvalidFileNameChars() ) @@ -739,7 +732,7 @@ private void InitializeQuickLinks() string driveName; if( !defaultPathInitialized ) { - DEFAULT_PATH = drives[i]; + defaultInitialPath = drives[i]; defaultPathInitialized = true; driveName = "Primary Drive"; @@ -809,7 +802,7 @@ private void InitializeQuickLinks() } else { - AddQuickLink( driveIcon, SAF_PICK_FOLDER_QUICK_LINK_TEXT, SAF_PICK_FOLDER_QUICK_LINK_PATH, ref anchoredPos ); + AddQuickLink( driveIcon, PickFolderQuickLinkText, SAF_PICK_FOLDER_QUICK_LINK_PATH, ref anchoredPos ); try { @@ -884,7 +877,7 @@ public void OnSubmitButtonClicked() string filenameInput = filenameInputField.text.Trim(); if( filenameInput.Length == 0 ) { - if( m_folderSelectMode ) + if( m_pickerMode != PickMode.Files ) OnOperationSuccessful( new string[1] { m_currentPath } ); else filenameImage.color = wrongFilenameColor; @@ -899,7 +892,7 @@ public void OnSubmitButtonClicked() // selectedFileEntries instead of filenameInputField // Beforehand, check if a folder is selected in file selection mode. If so, open that directory - if( !m_folderSelectMode ) + if( m_pickerMode == PickMode.Files ) { for( int i = 0; i < selectedFileEntries.Count; i++ ) { @@ -955,7 +948,7 @@ public void OnSubmitButtonClicked() fileCount++; else { - if( m_folderSelectMode ) + if( m_pickerMode != PickMode.Files ) fileCount++; else { @@ -994,37 +987,37 @@ public void OnSubmitButtonClicked() } else { - // This is a nonexisting file - string filename = filenameInput.Substring( startIndex, filenameLength ); - if( !m_folderSelectMode && filters[filtersDropdown.value].defaultExtension != null ) + try { - // In file selection mode, make sure that nonexisting files' extensions match one of the required extensions - string fileExtension = Path.GetExtension( filename ); - if( string.IsNullOrEmpty( fileExtension ) || !filters[filtersDropdown.value].extensions.Contains( fileExtension.ToLowerInvariant() ) ) - filename = Path.ChangeExtension( filename, filters[filtersDropdown.value].defaultExtension ); - } + // This is a nonexisting file + string filename = filenameInput.Substring( startIndex, filenameLength ); + if( m_pickerMode != PickMode.Folders && filters[filtersDropdown.value].defaultExtension != null ) + { + // In file selection mode, make sure that nonexisting files' extensions match one of the required extensions + string fileExtension = Path.GetExtension( filename ); + if( string.IsNullOrEmpty( fileExtension ) || !filters[filtersDropdown.value].extensions.Contains( fileExtension.ToLowerInvariant() ) ) + filename = Path.ChangeExtension( filename, filters[filtersDropdown.value].defaultExtension ); + } #if !UNITY_EDITOR && UNITY_ANDROID - if( FileBrowserHelpers.ShouldUseSAF ) - { - if( m_folderSelectMode ) - result[fileCount++] = FileBrowserHelpers.CreateFolderInDirectory( m_currentPath, filename ); + if( FileBrowserHelpers.ShouldUseSAF ) + { + if( m_pickerMode == PickMode.Folders ) + result[fileCount++] = FileBrowserHelpers.CreateFolderInDirectory( m_currentPath, filename ); + else + result[fileCount++] = FileBrowserHelpers.CreateFileInDirectory( m_currentPath, filename ); + } else - result[fileCount++] = FileBrowserHelpers.CreateFileInDirectory( m_currentPath, filename ); - } - else #endif - { - try { result[fileCount++] = Path.Combine( m_currentPath, filename ); } - catch( ArgumentException e ) - { - filenameImage.color = wrongFilenameColor; - Debug.LogException( e ); - return; - } + } + catch( ArgumentException e ) + { + filenameImage.color = wrongFilenameColor; + Debug.LogException( e ); + return; } } @@ -1281,7 +1274,7 @@ private void FetchPersistedSAFQuickLinks( ref Vector2 anchoredPos ) if( AddQuickLink( folderIcon, entryName, rawUri, ref anchoredPos ) && !defaultPathInitialized ) { - DEFAULT_PATH = rawUri; + defaultInitialPath = rawUri; defaultPathInitialized = true; } } @@ -1307,7 +1300,7 @@ private void OnFilenameInputChanged( string text ) #endregion #region Helper Functions - public void Show( string initialPath ) + public void Show( string initialPath, string initialFilename ) { if( AskPermissions ) RequestPermission(); @@ -1326,10 +1319,6 @@ public void Show( string initialPath ) filesScrollRect.verticalNormalizedPosition = 1; - filenameInputField.text = string.Empty; - filenameInputField.interactable = true; - filenameImage.color = Color.white; - IsOpen = true; Success = false; Result = null; @@ -1337,6 +1326,10 @@ public void Show( string initialPath ) gameObject.SetActive( true ); CurrentPath = GetInitialPath( initialPath ); + + filenameInputField.text = initialFilename ?? string.Empty; + filenameInputField.interactable = true; + filenameImage.color = Color.white; } public void Hide() @@ -1376,6 +1369,20 @@ public void RefreshFiles( bool pathChanged ) if( allFileEntries != null ) { + if( sortFilesByName ) + { + // Sort the files and folders in the following order: + // 1. Directories come before files + // 2. Directories and files are sorted by their names + Array.Sort( allFileEntries, ( entry1, entry2 ) => + { + if( entry1.IsDirectory != entry2.IsDirectory ) + return entry1.IsDirectory ? -1 : 1; + else + return entry1.Name.CompareTo( entry2.Name ); + } ); + } + for( int i = 0; i < allFileEntries.Length; i++ ) { try @@ -1384,7 +1391,7 @@ public void RefreshFiles( bool pathChanged ) if( !item.IsDirectory ) { - if( m_folderSelectMode ) + if( m_pickerMode == PickMode.Folders ) continue; if( ( item.Attributes & ignoredFileAttributes ) != 0 ) @@ -1529,7 +1536,7 @@ private IEnumerator CreateNewFolderCoroutine() RefreshFiles( true ); - if( m_folderSelectMode ) + if( m_pickerMode != PickMode.Files ) filenameInputField.text = folderName; // Focus on the newly created folder @@ -1603,7 +1610,7 @@ public void RenameSelectedFile() RefreshFiles( true ); - if( fileInfo.IsDirectory == m_folderSelectMode ) + if( ( fileInfo.IsDirectory && m_pickerMode != PickMode.Files ) || ( !fileInfo.IsDirectory && m_pickerMode != PickMode.Folders ) ) filenameInputField.text = newName; } ); } @@ -1804,7 +1811,7 @@ private void UpdateFilenameInputFieldWithSelection() // 1 file selected: file.Name // 2+ files selected: "file1.Name" "file2.Name" ... (up to FILENAME_INPUT_FIELD_MAX_FILE_COUNT filenames are displayed for performance reasons) int filenameContributingFileCount = 0; - if( FolderSelectMode ) + if( m_pickerMode != PickMode.Files ) filenameContributingFileCount = selectedFileEntries.Count; else { @@ -1837,7 +1844,7 @@ private void UpdateFilenameInputFieldWithSelection() for( int i = 0, fileCount = 0; i < selectedFileEntries.Count; i++ ) { FileSystemEntry selectedFile = validFileEntries[selectedFileEntries[i]]; - if( FolderSelectMode || !selectedFile.IsDirectory ) + if( m_pickerMode != PickMode.Files || !selectedFile.IsDirectory ) { if( filenameContributingFileCount == 1 ) { @@ -1972,7 +1979,7 @@ private string GetInitialPath( string initialPath ) if( string.IsNullOrEmpty( initialPath ) || !Directory.Exists( initialPath ) ) { if( CurrentPath.Length == 0 ) - initialPath = DEFAULT_PATH; + initialPath = defaultInitialPath; else initialPath = CurrentPath; } @@ -1985,33 +1992,24 @@ private string GetInitialPath( string initialPath ) #region File Browser Functions (static) public static bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, - bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, + PickMode pickMode, bool allowMultiSelection = false, + string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ) { - // Instead of ignoring this dialog request, let's just override the currently visible dialog's properties - //if( Instance.gameObject.activeSelf ) - //{ - // Debug.LogError( "Error: Multiple dialogs are not allowed!" ); - // return false; - //} - - Instance.onSuccess = onSuccess; - Instance.onCancel = onCancel; - - Instance.FolderSelectMode = folderMode; - Instance.AllowMultiSelection = allowMultiSelection; - Instance.Title = title; - Instance.SubmitButtonText = saveButtonText; - Instance.AcceptNonExistingFilename = !folderMode; - - Instance.Show( initialPath ); - - return true; + return ShowDialogInternal( onSuccess, onCancel, pickMode, allowMultiSelection, pickMode != PickMode.Folders, initialPath, initialFilename, title, saveButtonText ); } public static bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, - bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, + PickMode pickMode, bool allowMultiSelection = false, + string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ) + { + return ShowDialogInternal( onSuccess, onCancel, pickMode, allowMultiSelection, false, initialPath, initialFilename, title, loadButtonText ); + } + + private static bool ShowDialogInternal( OnSuccess onSuccess, OnCancel onCancel, + PickMode pickMode, bool allowMultiSelection, bool acceptNonExistingFilename, + string initialPath, string initialFilename, string title, string submitButtonText ) { // Instead of ignoring this dialog request, let's just override the currently visible dialog's properties //if( Instance.gameObject.activeSelf ) @@ -2023,13 +2021,13 @@ public static bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, Instance.onSuccess = onSuccess; Instance.onCancel = onCancel; - Instance.FolderSelectMode = folderMode; + Instance.PickerMode = pickMode; Instance.AllowMultiSelection = allowMultiSelection; Instance.Title = title; - Instance.SubmitButtonText = loadButtonText; - Instance.AcceptNonExistingFilename = false; + Instance.SubmitButtonText = submitButtonText; + Instance.AcceptNonExistingFilename = acceptNonExistingFilename; - Instance.Show( initialPath ); + Instance.Show( initialPath, initialFilename ); return true; } @@ -2039,20 +2037,22 @@ public static void HideDialog( bool invokeCancelCallback = false ) Instance.OnOperationCanceled( invokeCancelCallback ); } - public static IEnumerator WaitForSaveDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, + public static IEnumerator WaitForSaveDialog( PickMode pickMode, bool allowMultiSelection = false, + string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" ) { - if( !ShowSaveDialog( null, null, folderMode, allowMultiSelection, initialPath, title, saveButtonText ) ) + if( !ShowSaveDialog( null, null, pickMode, allowMultiSelection, initialPath, initialFilename, title, saveButtonText ) ) yield break; while( Instance.gameObject.activeSelf ) yield return null; } - public static IEnumerator WaitForLoadDialog( bool folderMode = false, bool allowMultiSelection = false, string initialPath = null, + public static IEnumerator WaitForLoadDialog( PickMode pickMode, bool allowMultiSelection = false, + string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" ) { - if( !ShowLoadDialog( null, null, folderMode, allowMultiSelection, initialPath, title, loadButtonText ) ) + if( !ShowLoadDialog( null, null, pickMode, allowMultiSelection, initialPath, initialFilename, title, loadButtonText ) ) yield break; while( Instance.gameObject.activeSelf ) diff --git a/package.json b/package.json index 5b2f390..a8550c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.yasirkula.simplefilebrowser", "displayName": "Simple File Browser", - "version": "1.3.7", + "version": "1.3.9", "description": "This plugin helps you show save/load dialogs during gameplay with its uGUI based file browser." } \ No newline at end of file