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 0000000..2a86e2a Binary files /dev/null and b/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.aar differ 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 33bcb31..0000000 Binary files a/Plugins/SimpleFileBrowser/Android/SimpleFileBrowser.jar and /dev/null differ 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