Структуры

This commit is contained in:
ThomasCharester committed 2026-02-26 13:19:15 +03:00
1 parent 06b08f1efe
commit 1b578067f3
82 files changed
+2692 -944

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: fbefdb800f5c8e4468bf456469caaf61
folderAsset: yes
timeCreated: 1596983915
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e5c273c78eb8ebb47a9c6005d4041d73
folderAsset: yes
timeCreated: 1596971200
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
#if UNITY_EDITOR || UNITY_ANDROID
using System;
using UnityEngine;
namespace NativeFilePickerNamespace
{
public class FPCallbackHelper : MonoBehaviour
{
private bool autoDestroyWithCallback;
private Action mainThreadAction = null;
public static FPCallbackHelper Create( bool autoDestroyWithCallback )
{
FPCallbackHelper result = new GameObject( "FPCallbackHelper" ).AddComponent<FPCallbackHelper>();
result.autoDestroyWithCallback = autoDestroyWithCallback;
DontDestroyOnLoad( result.gameObject );
return result;
}
public void CallOnMainThread( Action function )
{
lock( this )
{
mainThreadAction += function;
}
}
private void Update()
{
if( mainThreadAction != null )
{
try
{
Action temp;
lock( this )
{
temp = mainThreadAction;
mainThreadAction = null;
}
temp();
}
finally
{
if( autoDestroyWithCallback )
Destroy( gameObject );
}
}
}
}
}
#endif
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 41423473566db0b4ebd62882996f08bc
timeCreated: 1596973837
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Android/FPCallbackHelper.cs
uploadId: 757770
@@ -0,0 +1,24 @@
#if UNITY_EDITOR || UNITY_ANDROID
using UnityEngine;
namespace NativeFilePickerNamespace
{
public class FPPermissionCallbackAndroid : AndroidJavaProxy
{
private readonly NativeFilePicker.PermissionCallback callback;
private readonly FPCallbackHelper callbackHelper;
public FPPermissionCallbackAndroid( NativeFilePicker.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeFilePickerPermissionReceiver" )
{
this.callback = callback;
callbackHelper = FPCallbackHelper.Create( true );
}
[UnityEngine.Scripting.Preserve]
public void OnPermissionResult( int result )
{
callbackHelper.CallOnMainThread( () => callback( (NativeFilePicker.Permission) result ) );
}
}
}
#endif
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 5f8c2de0178a90140b030975551436a5
timeCreated: 1596973837
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Android/FPPermissionCallbackAndroid.cs
uploadId: 757770
@@ -0,0 +1,71 @@
#if UNITY_EDITOR || UNITY_ANDROID
using UnityEngine;
namespace NativeFilePickerNamespace
{
public class FPResultCallbackAndroid : AndroidJavaProxy
{
private readonly NativeFilePicker.FilePickedCallback pickCallback;
private readonly NativeFilePicker.MultipleFilesPickedCallback pickCallbackMultiple;
private readonly NativeFilePicker.FilesExportedCallback exportCallback;
private readonly FPCallbackHelper callbackHelper;
public FPResultCallbackAndroid( NativeFilePicker.FilePickedCallback pickCallback, NativeFilePicker.MultipleFilesPickedCallback pickCallbackMultiple, NativeFilePicker.FilesExportedCallback exportCallback ) : base( "com.yasirkula.unity.NativeFilePickerResultReceiver" )
{
this.pickCallback = pickCallback;
this.pickCallbackMultiple = pickCallbackMultiple;
this.exportCallback = exportCallback;
callbackHelper = FPCallbackHelper.Create( true );
}
[UnityEngine.Scripting.Preserve]
public void OnFilePicked( string path )
{
callbackHelper.CallOnMainThread( () => pickCallback( !string.IsNullOrEmpty( path ) ? path : null ) );
}
[UnityEngine.Scripting.Preserve]
public void OnMultipleFilesPicked( string paths )
{
string[] result = null;
if( !string.IsNullOrEmpty( paths ) )
{
string[] pathsSplit = paths.Split( '>' );
int validPathCount = 0;
for( int i = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPathCount++;
}
if( validPathCount == 0 )
pathsSplit = new string[0];
else if( validPathCount != pathsSplit.Length )
{
string[] validPaths = new string[validPathCount];
for( int i = 0, j = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPaths[j++] = pathsSplit[i];
}
pathsSplit = validPaths;
}
result = pathsSplit;
}
callbackHelper.CallOnMainThread( () => pickCallbackMultiple( ( result != null && result.Length > 0 ) ? result : null ) );
}
[UnityEngine.Scripting.Preserve]
public void OnFilesExported( bool result )
{
callbackHelper.CallOnMainThread( () => exportCallback( result ) );
}
}
}
#endif
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: f43d316f7eb1cdf4abcf327a1fbc025b
timeCreated: 1596973837
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Android/FPResultCallbackAndroid.cs
uploadId: 757770
@@ -0,0 +1,40 @@
fileFormatVersion: 2
guid: 3ed817dcf924721488f9672995f51cb3
timeCreated: 1607368610
licenseType: Store
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
Android: Android
second:
enabled: 1
settings: {}
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Android/NativeFilePicker.aar
uploadId: 757770
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f387670006405804d9b5c216ebd8ab1f
folderAsset: yes
timeCreated: 1521452097
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
{
"name": "NativeFilePicker.Editor",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 31a0463be93a160428f78ae484a99f5b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Editor/NativeFilePicker.Editor.asmdef
uploadId: 757770
@@ -0,0 +1,65 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace NativeFilePickerNamespace
{
[HelpURL( "https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_declare/understand_utis_declare.html" )]
public class NativeFilePickerCustomTypes : ScriptableObject
{
[System.Serializable]
public class TypeHolder
{
[Tooltip( "A unique identifier for the type (UTI) (e.g. com.mycompany.mydata)" )]
public string identifier;
[Tooltip( "Description of the type (e.g. Save data for My App)" )]
public string description;
[Tooltip( "If this type is owned by your app (i.e. exclusive to your app), set this value to true. If another app generates files of this type and your app is merely importing/modifying them, set this value to false." )]
public bool isExported;
[Tooltip( "Which official UTI(s) is this type part of (e.g. public.data for generic types or public.image for image types)\nFull list available at: https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html" )]
public string[] conformsTo;
[Tooltip( "Extension(s) associated with this type (don't include the period; i.e. use myextension instead of .myextension)" )]
public string[] extensions;
}
private const string INITIAL_SAVE_PATH = "Assets/Plugins/NativeFilePicker/NativeFilePickerCustomTypes.asset";
#pragma warning disable 0649
[SerializeField]
private TypeHolder[] customTypes;
#pragma warning restore 0649
[MenuItem( "Window/NativeFilePicker Custom Types" )]
private static void InspectCustomTypes()
{
Selection.activeObject = GetInstance( true );
}
private static NativeFilePickerCustomTypes GetInstance( bool createIfNotExists )
{
NativeFilePickerCustomTypes instance = null;
string[] instances = AssetDatabase.FindAssets( "t:NativeFilePickerCustomTypes" );
if( instances != null && instances.Length > 0 )
instance = AssetDatabase.LoadAssetAtPath<NativeFilePickerCustomTypes>( AssetDatabase.GUIDToAssetPath( instances[0] ) );
if( !instance && createIfNotExists )
{
Directory.CreateDirectory( Path.GetDirectoryName( INITIAL_SAVE_PATH ) );
AssetDatabase.CreateAsset( CreateInstance<NativeFilePickerCustomTypes>(), INITIAL_SAVE_PATH );
AssetDatabase.SaveAssets();
instance = AssetDatabase.LoadAssetAtPath<NativeFilePickerCustomTypes>( INITIAL_SAVE_PATH );
Debug.Log( "Created NativeFilePickerCustomTypes file at " + INITIAL_SAVE_PATH + ". You can move this file around freely.", instance );
}
return instance;
}
public static TypeHolder[] GetCustomTypes()
{
NativeFilePickerCustomTypes instance = GetInstance( false );
return instance ? instance.customTypes : null;
}
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: f281670325986984c95fde82c23195e0
timeCreated: 1592237398
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Editor/NativeFilePickerCustomTypes.cs
uploadId: 757770
@@ -0,0 +1,199 @@
using System.IO;
using UnityEditor;
using UnityEngine;
#if UNITY_IOS
using System.Collections.Generic;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
#endif
namespace NativeFilePickerNamespace
{
[System.Serializable]
public class Settings
{
private const string SAVE_PATH = "ProjectSettings/NativeFilePicker.json";
public bool AutoSetupFrameworks = true;
public bool AutoSetupiCloud = false;
private static Settings m_instance = null;
public static Settings Instance
{
get
{
if( m_instance == null )
{
try
{
if( File.Exists( SAVE_PATH ) )
m_instance = JsonUtility.FromJson<Settings>( File.ReadAllText( SAVE_PATH ) );
else
m_instance = new Settings();
}
catch( System.Exception e )
{
Debug.LogException( e );
m_instance = new Settings();
}
}
return m_instance;
}
}
public void Save()
{
File.WriteAllText( SAVE_PATH, JsonUtility.ToJson( this, true ) );
}
[SettingsProvider]
public static SettingsProvider CreatePreferencesGUI()
{
return new SettingsProvider( "Project/yasirkula/Native File Picker", SettingsScope.Project )
{
guiHandler = ( searchContext ) => PreferencesGUI(),
keywords = new System.Collections.Generic.HashSet<string>() { "Native", "File", "Picker", "Android", "iOS" }
};
}
public static void PreferencesGUI()
{
EditorGUI.BeginChangeCheck();
Instance.AutoSetupFrameworks = EditorGUILayout.Toggle( new GUIContent( "Auto Setup Frameworks", "Automatically adds MobileCoreServices and CloudKit frameworks to the generated Xcode project" ), Instance.AutoSetupFrameworks );
Instance.AutoSetupiCloud = EditorGUILayout.Toggle( new GUIContent( "Auto Setup iCloud", "Automatically enables iCloud capability of the generated Xcode project" ), Instance.AutoSetupiCloud );
if( EditorGUI.EndChangeCheck() )
Instance.Save();
}
}
public class NativeFilePickerPostProcessBuild
{
#if UNITY_IOS
#pragma warning disable 0162
[PostProcessBuild]
public static void OnPostprocessBuild( BuildTarget target, string buildPath )
{
// Add declared custom types to Info.plist
if( target == BuildTarget.iOS )
{
NativeFilePickerCustomTypes.TypeHolder[] customTypes = NativeFilePickerCustomTypes.GetCustomTypes();
if( customTypes != null )
{
string plistPath = Path.Combine( buildPath, "Info.plist" );
PlistDocument plist = new PlistDocument();
plist.ReadFromString( File.ReadAllText( plistPath ) );
PlistElementDict rootDict = plist.root;
for( int i = 0; i < customTypes.Length; i++ )
{
NativeFilePickerCustomTypes.TypeHolder customType = customTypes[i];
PlistElementArray customTypesArray = GetCustomTypesArray( rootDict, customType.isExported );
// Don't allow duplicate entries
RemoveCustomTypeIfExists( customTypesArray, customType.identifier );
PlistElementDict customTypeDict = customTypesArray.AddDict();
customTypeDict.SetString( "UTTypeIdentifier", customType.identifier );
customTypeDict.SetString( "UTTypeDescription", customType.description );
PlistElementArray conformsTo = customTypeDict.CreateArray( "UTTypeConformsTo" );
for( int j = 0; j < customType.conformsTo.Length; j++ )
conformsTo.AddString( customType.conformsTo[j] );
PlistElementDict tagSpecification = customTypeDict.CreateDict( "UTTypeTagSpecification" );
PlistElementArray tagExtensions = tagSpecification.CreateArray( "public.filename-extension" );
for( int j = 0; j < customType.extensions.Length; j++ )
tagExtensions.AddString( customType.extensions[j] );
}
File.WriteAllText( plistPath, plist.WriteToString() );
}
}
// Rest of the function shouldn't execute unless build post-processing is enabled
if( !Settings.Instance.AutoSetupFrameworks && !Settings.Instance.AutoSetupiCloud )
return;
if( target == BuildTarget.iOS )
{
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
PBXProject pbxProject = new PBXProject();
pbxProject.ReadFromFile( pbxProjectPath );
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
if( Settings.Instance.AutoSetupFrameworks )
{
pbxProject.AddFrameworkToProject( targetGUID, "MobileCoreServices.framework", false );
pbxProject.AddFrameworkToProject( targetGUID, "CloudKit.framework", false );
}
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
if( Settings.Instance.AutoSetupiCloud )
{
ProjectCapabilityManager manager = new ProjectCapabilityManager( pbxProjectPath, "iCloud.entitlements", "Unity-iPhone" );
manager.AddiCloud( false, true, false, true, null );
manager.WriteToFile();
}
}
}
// Adding PRODUCT_BUNDLE_IDENTIFIER if not exists (if another plugin also fills this value, we must not touch it)
[PostProcessBuild( 99 )]
public static void OnPostprocessBuild2( BuildTarget target, string buildPath )
{
if( !Settings.Instance.AutoSetupFrameworks && !Settings.Instance.AutoSetupiCloud )
return;
if( target == BuildTarget.iOS )
{
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
PBXProject pbxProject = new PBXProject();
pbxProject.ReadFromFile( pbxProjectPath );
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
if( string.IsNullOrEmpty( pbxProject.GetBuildPropertyForAnyConfig( targetGUID, "PRODUCT_BUNDLE_IDENTIFIER" ) ) )
pbxProject.AddBuildProperty( targetGUID, "PRODUCT_BUNDLE_IDENTIFIER", PlayerSettings.applicationIdentifier );
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
}
}
private static PlistElementArray GetCustomTypesArray( PlistElementDict rootDict, bool isExported )
{
string key = isExported ? "UTExportedTypeDeclarations" : "UTImportedTypeDeclarations";
PlistElementArray result = rootDict[key] as PlistElementArray;
if( result == null )
result = rootDict.CreateArray( key );
return result;
}
private static void RemoveCustomTypeIfExists( PlistElementArray customTypesArray, string UTI )
{
List<PlistElement> values = customTypesArray.values;
if( values == null )
return;
for( int i = values.Count - 1; i >= 0; i-- )
{
PlistElementDict exportedType = values[i] as PlistElementDict;
if( exportedType != null )
{
PlistElementString exportedTypeID = exportedType["UTTypeIdentifier"] as PlistElementString;
if( exportedTypeID != null && exportedTypeID.value == UTI )
values.RemoveAt( i );
}
}
}
#pragma warning restore 0162
#endif
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: d466ecf51c239f9419715f6b59348d6c
timeCreated: 1521452119
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/Editor/NativeFilePickerPostProcessBuild.cs
uploadId: 757770
@@ -0,0 +1,3 @@
{
"name": "NativeFilePicker.Runtime"
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 250e096504da3aa4f851a4df12262950
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/NativeFilePicker.Runtime.asmdef
uploadId: 757770
@@ -0,0 +1,411 @@
using System;
using System.IO;
using UnityEngine;
using System.Threading.Tasks;
#if UNITY_ANDROID || UNITY_IOS
using NativeFilePickerNamespace;
#endif
public static class NativeFilePicker
{
public delegate void PermissionCallback( Permission permission );
public delegate void FilePickedCallback( string path );
public delegate void MultipleFilesPickedCallback( string[] paths );
public delegate void FilesExportedCallback( bool success );
public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
#region Platform Specific Elements
#if !UNITY_EDITOR && UNITY_ANDROID
private static AndroidJavaClass m_ajc = null;
private static AndroidJavaClass AJC
{
get
{
if( m_ajc == null )
m_ajc = new AndroidJavaClass( "com.yasirkula.unity.NativeFilePicker" );
return m_ajc;
}
}
private static AndroidJavaObject m_context = null;
private static AndroidJavaObject Context
{
get
{
if( m_context == null )
{
using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
{
m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
}
}
return m_context;
}
}
#elif !UNITY_EDITOR && UNITY_IOS
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeFilePicker_CanPickMultipleFiles();
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern string _NativeFilePicker_ConvertExtensionToUTI( string extension );
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern void _NativeFilePicker_PickFile( string[] UTIs, int UTIsCount );
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern void _NativeFilePicker_PickMultipleFiles( string[] UTIs, int UTIsCount );
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern void _NativeFilePicker_ExportFiles( string[] files, int filesCount );
#endif
#endregion
#if !UNITY_EDITOR && UNITY_ANDROID
private static string m_selectedFilePath = null;
private static string SelectedFilePath
{
get
{
if( m_selectedFilePath == null )
{
m_selectedFilePath = Path.Combine( Application.temporaryCachePath, "pickedFile" );
Directory.CreateDirectory( Application.temporaryCachePath );
}
return m_selectedFilePath;
}
}
#endif
#region Runtime Permissions
public static bool CheckPermission( bool readPermissionOnly = false )
{
#if !UNITY_EDITOR && UNITY_ANDROID
return AJC.CallStatic<int>( "CheckPermission", Context, readPermissionOnly ) == 1;
#else
return true;
#endif
}
public static void RequestPermissionAsync( PermissionCallback callback, bool readPermissionOnly = false )
{
#if !UNITY_EDITOR && UNITY_ANDROID
FPPermissionCallbackAndroid nativeCallback = new( callback );
AJC.CallStatic( "RequestPermission", Context, nativeCallback, readPermissionOnly );
#else
callback( Permission.Granted );
#endif
}
public static Task<Permission> RequestPermissionAsync( bool readPermissionOnly = false )
{
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), readPermissionOnly );
return tcs.Task;
}
public static void OpenSettings()
{
#if !UNITY_EDITOR && UNITY_ANDROID
AJC.CallStatic( "OpenSettings", Context );
#endif
}
#endregion
#region Helper Functions
public static bool CanPickMultipleFiles()
{
#if !UNITY_EDITOR && UNITY_ANDROID
return AJC.CallStatic<bool>( "CanPickMultipleFiles" );
#elif !UNITY_EDITOR && UNITY_IOS
return _NativeFilePicker_CanPickMultipleFiles() == 1;
#else
return false;
#endif
}
public static bool CanExportFiles()
{
#if UNITY_EDITOR
return true;
#elif UNITY_ANDROID
return AJC.CallStatic<bool>( "CanExportFiles" );
#elif UNITY_IOS
return true;
#else
return false;
#endif
}
public static bool CanExportMultipleFiles()
{
#if UNITY_EDITOR
return true;
#elif UNITY_ANDROID
return AJC.CallStatic<bool>( "CanExportMultipleFiles" );
#elif UNITY_IOS
return _NativeFilePicker_CanPickMultipleFiles() == 1;
#else
return false;
#endif
}
public static bool IsFilePickerBusy()
{
#if !UNITY_EDITOR && UNITY_IOS
return FPResultCallbackiOS.IsBusy;
#else
return false;
#endif
}
public static string ConvertExtensionToFileType( string extension )
{
if( string.IsNullOrEmpty( extension ) )
return null;
if( extension.IndexOf( '*' ) >= 0 )
{
// So many users try to do this that it's now necessary to throw an exception for this particular scenario
throw new ArgumentException( "See: https://github.com/yasirkula/UnityNativeFilePicker#faq" );
}
#if !UNITY_EDITOR && UNITY_ANDROID
return AJC.CallStatic<string>( "GetMimeTypeFromExtension", extension.ToLowerInvariant() );
#elif !UNITY_EDITOR && UNITY_IOS
return _NativeFilePicker_ConvertExtensionToUTI( extension.ToLowerInvariant() );
#else
return extension;
#endif
}
#endregion
#region Import Functions
public static void PickFile( FilePickedCallback callback, params string[] allowedFileTypes )
{
// If no file type is specified, allow all file types
if( allowedFileTypes == null || allowedFileTypes.Length == 0 )
{
#if UNITY_ANDROID
allowedFileTypes = new string[] { "*/*" };
#else
allowedFileTypes = new string[] { "public.item", "public.content" };
#endif
}
RequestPermissionAsync( ( permission ) =>
{
if( permission != Permission.Granted || IsFilePickerBusy() )
{
callback?.Invoke( null );
return;
}
#if UNITY_EDITOR
// Accept Android and iOS UTIs when possible, for user's convenience
string[] editorFilters = new string[allowedFileTypes.Length * 2];
for( int i = 0; i < allowedFileTypes.Length; i++ )
{
if( allowedFileTypes[i].IndexOf( '*' ) >= 0 )
{
if( allowedFileTypes[i] == "image/*" )
{
editorFilters[i * 2] = "Image files";
editorFilters[i * 2 + 1] = "png,jpg,jpeg";
}
else if( allowedFileTypes[i] == "video/*" )
{
editorFilters[i * 2] = "Video files";
editorFilters[i * 2 + 1] = "mp4,mov,webm,avi";
}
else if( allowedFileTypes[i] == "audio/*" )
{
editorFilters[i * 2] = "Audio files";
editorFilters[i * 2 + 1] = "mp3,wav,aac,flac";
}
else
{
editorFilters[i * 2] = "All files";
editorFilters[i * 2 + 1] = "*";
}
}
else
{
editorFilters[i * 2] = allowedFileTypes[i];
if( allowedFileTypes[i].IndexOf( '/' ) >= 0 ) // Android UTIs like 'image/png'
editorFilters[i * 2 + 1] = allowedFileTypes[i].Substring( allowedFileTypes[i].IndexOf( '/' ) + 1 );
else if( allowedFileTypes[i].StartsWith( "public." ) ) // iOS UTIs like 'public.png'
editorFilters[i * 2 + 1] = allowedFileTypes[i].Substring( 7 );
else if( allowedFileTypes[i].IndexOf( '.' ) == 0 ) // Extensions starting with period like '.png'
editorFilters[i * 2 + 1] = allowedFileTypes[i].Substring( 1 );
else
editorFilters[i * 2 + 1] = allowedFileTypes[i];
}
}
string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select file", "", editorFilters );
if( callback != null )
callback( pickedFile != "" ? pickedFile : null );
#elif UNITY_ANDROID
AJC.CallStatic( "PickFiles", Context, new FPResultCallbackAndroid( callback, null, null ), false, SelectedFilePath, allowedFileTypes, "" );
#elif UNITY_IOS
FPResultCallbackiOS.Initialize( callback, null, null );
_NativeFilePicker_PickFile( allowedFileTypes, allowedFileTypes.Length );
#else
if( callback != null )
callback( null );
#endif
}, true );
}
public static void PickMultipleFiles( MultipleFilesPickedCallback callback, params string[] allowedFileTypes )
{
// If no file type is specified, allow all file types
if( allowedFileTypes == null || allowedFileTypes.Length == 0 )
{
#if UNITY_ANDROID
allowedFileTypes = new string[] { "*/*" };
#else
allowedFileTypes = new string[] { "public.item", "public.content" };
#endif
}
RequestPermissionAsync( ( permission ) =>
{
if( permission != Permission.Granted || IsFilePickerBusy() )
{
callback?.Invoke( null );
return;
}
if( CanPickMultipleFiles() )
{
#if !UNITY_EDITOR && UNITY_ANDROID
AJC.CallStatic( "PickFiles", Context, new FPResultCallbackAndroid( null, callback, null ), true, SelectedFilePath, allowedFileTypes, "" );
#elif !UNITY_EDITOR && UNITY_IOS
FPResultCallbackiOS.Initialize( null, callback, null );
_NativeFilePicker_PickMultipleFiles( allowedFileTypes, allowedFileTypes.Length );
#endif
}
else if( callback != null )
callback( null );
}, true );
}
#endregion
#region Export Functions
public static void ExportFile( string filePath, FilesExportedCallback callback = null )
{
if( string.IsNullOrEmpty( filePath ) )
throw new ArgumentException( "Parameter 'filePath' is null or empty!" );
RequestPermissionAsync( ( permission ) =>
{
if( permission != Permission.Granted || IsFilePickerBusy() )
{
callback?.Invoke( false );
return;
}
if( CanExportFiles() )
{
#if UNITY_EDITOR
string extension = Path.GetExtension( filePath );
if( extension == null )
extension = "";
else if( extension.IndexOf( '.' ) == 0 )
extension = extension.Substring( 1 );
string destination = UnityEditor.EditorUtility.SaveFilePanel( "Select destination", Path.GetDirectoryName( filePath ), Path.GetFileName( filePath ), extension );
if( string.IsNullOrEmpty( destination ) )
{
if( callback != null )
callback( false );
}
else
{
try
{
File.Copy( filePath, destination, true );
if( callback != null )
callback( true );
}
catch( Exception e )
{
Debug.LogException( e );
if( callback != null )
callback( false );
}
}
#elif UNITY_ANDROID
AJC.CallStatic( "ExportFiles", Context, new FPResultCallbackAndroid( null, null, callback ), new string[1] { filePath }, 1 );
#elif UNITY_IOS
FPResultCallbackiOS.Initialize( null, null, callback );
_NativeFilePicker_ExportFiles( new string[1] { filePath }, 1 );
#endif
}
else if( callback != null )
callback( false );
}, false );
}
public static void ExportMultipleFiles( string[] filePaths, FilesExportedCallback callback = null )
{
if( filePaths == null || filePaths.Length == 0 )
throw new ArgumentException( "Parameter 'filePaths' is null or empty!" );
RequestPermissionAsync( ( permission ) =>
{
if( permission != Permission.Granted || IsFilePickerBusy() )
{
callback?.Invoke( false );
return;
}
if( CanExportMultipleFiles() )
{
#if UNITY_EDITOR
string destination = UnityEditor.EditorUtility.OpenFolderPanel( "Select destination", Path.GetDirectoryName( filePaths[0] ), "" );
if( string.IsNullOrEmpty( destination ) )
{
if( callback != null )
callback( false );
}
else
{
try
{
for( int i = 0; i < filePaths.Length; i++ )
File.Copy( filePaths[i], Path.Combine( destination, Path.GetFileName( filePaths[i] ) ), true );
if( callback != null )
callback( true );
}
catch( Exception e )
{
Debug.LogException( e );
if( callback != null )
callback( false );
}
}
#elif UNITY_ANDROID
AJC.CallStatic( "ExportFiles", Context, new FPResultCallbackAndroid( null, null, callback ), filePaths, filePaths.Length );
#elif UNITY_IOS
FPResultCallbackiOS.Initialize( null, null, callback );
_NativeFilePicker_ExportFiles( filePaths, filePaths.Length );
#endif
}
else if( callback != null )
callback( false );
}, false );
}
#endregion
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 5ecdc359fc7b7e448a5aa6433eafcb8b
timeCreated: 1498722610
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/NativeFilePicker.cs
uploadId: 757770
@@ -0,0 +1,6 @@
= Native File Picker for Android & iOS (v1.4.1) =
Documentation: https://github.com/yasirkula/UnityNativeFilePicker
FAQ: https://github.com/yasirkula/UnityNativeFilePicker#faq
Example code: https://github.com/yasirkula/UnityNativeFilePicker#example-code
E-mail: yasirkula@gmail.com
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: a0357092d6d35434c8b136abfe2b6260
timeCreated: 1563308465
licenseType: Store
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/README.txt
uploadId: 757770
+9
View File
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 8d99a20b76631e440a062e4fad822fc8
folderAsset: yes
timeCreated: 1592132993
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,155 @@
#if UNITY_EDITOR || UNITY_IOS
using UnityEngine;
namespace NativeFilePickerNamespace
{
public class FPResultCallbackiOS : MonoBehaviour
{
private static FPResultCallbackiOS instance;
private NativeFilePicker.FilePickedCallback pickCallback;
private NativeFilePicker.MultipleFilesPickedCallback pickCallbackMultiple;
private NativeFilePicker.FilesExportedCallback exportCallback;
private float nextBusyCheckTime;
public static bool IsBusy { get; private set; }
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeFilePicker_IsFilePickerBusy();
public static void Initialize( NativeFilePicker.FilePickedCallback pickCallback, NativeFilePicker.MultipleFilesPickedCallback pickCallbackMultiple, NativeFilePicker.FilesExportedCallback exportCallback )
{
if( IsBusy )
return;
if( instance == null )
{
instance = new GameObject( "FPResultCallbackiOS" ).AddComponent<FPResultCallbackiOS>();
DontDestroyOnLoad( instance.gameObject );
}
instance.pickCallback = pickCallback;
instance.pickCallbackMultiple = pickCallbackMultiple;
instance.exportCallback = exportCallback;
instance.nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
IsBusy = true;
}
private void Update()
{
if( IsBusy )
{
if( Time.realtimeSinceStartup >= nextBusyCheckTime )
{
nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
if( _NativeFilePicker_IsFilePickerBusy() == 0 )
OnOperationCancelled( null );
}
}
}
[UnityEngine.Scripting.Preserve]
public void OnFilePicked( string path )
{
IsBusy = false;
if( string.IsNullOrEmpty( path ) )
path = null;
NativeFilePicker.FilePickedCallback _pickCallback = pickCallback;
pickCallback = null;
if( _pickCallback != null )
_pickCallback( path );
}
[UnityEngine.Scripting.Preserve]
public void OnMultipleFilesPicked( string paths )
{
IsBusy = false;
string[] _paths = SplitPaths( paths );
if( _paths != null && _paths.Length == 0 )
_paths = null;
NativeFilePicker.MultipleFilesPickedCallback _pickCallbackMultiple = pickCallbackMultiple;
pickCallbackMultiple = null;
if( _pickCallbackMultiple != null )
_pickCallbackMultiple( _paths );
}
[UnityEngine.Scripting.Preserve]
public void OnFilesExported( string message )
{
IsBusy = false;
NativeFilePicker.FilesExportedCallback _exportCallback = exportCallback;
exportCallback = null;
if( _exportCallback != null )
_exportCallback( message == "1" );
}
[UnityEngine.Scripting.Preserve]
public void OnOperationCancelled( string message )
{
IsBusy = false;
NativeFilePicker.FilePickedCallback _pickCallback = pickCallback;
NativeFilePicker.MultipleFilesPickedCallback _pickCallbackMultiple = pickCallbackMultiple;
NativeFilePicker.FilesExportedCallback _exportCallback = exportCallback;
pickCallback = null;
pickCallbackMultiple = null;
exportCallback = null;
if( _pickCallback != null )
_pickCallback( null );
if( _pickCallbackMultiple != null )
_pickCallbackMultiple( null );
if( _exportCallback != null )
_exportCallback( false );
}
private string[] SplitPaths( string paths )
{
string[] result = null;
if( !string.IsNullOrEmpty( paths ) )
{
string[] pathsSplit = paths.Split( '>' );
int validPathCount = 0;
for( int i = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPathCount++;
}
if( validPathCount == 0 )
pathsSplit = new string[0];
else if( validPathCount != pathsSplit.Length )
{
string[] validPaths = new string[validPathCount];
for( int i = 0, j = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPaths[j++] = pathsSplit[i];
}
pathsSplit = validPaths;
}
result = pathsSplit;
}
return result;
}
}
}
#endif
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: b11d2999e16ca864f8974988d73c2017
timeCreated: 1519060539
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/iOS/FPResultCallbackiOS.cs
uploadId: 757770
@@ -0,0 +1,213 @@
#import <MobileCoreServices/MobileCoreServices.h>
extern UIViewController* UnityGetGLViewController();
@interface UNativeFilePicker:NSObject
+ (void)pickFiles:(BOOL)allowMultipleSelection withUTIs:(NSArray<NSString *> *)allowedUTIs;
+ (void)exportFiles:(NSArray<NSURL *> *)paths;
+ (int)canPickMultipleFiles;
+ (int)isFilePickerBusy;
+ (char *)convertExtensionToUTI:(NSString *)extension;
@end
@implementation UNativeFilePicker
static UIDocumentPickerViewController *filePicker;
static BOOL pickingMultipleFiles;
static int filePickerState = 0; // 0 -> none, 1 -> showing, 2 -> finished
+ (void)pickFiles:(BOOL)allowMultipleSelection withUTIs:(NSArray<NSString *> *)allowedUTIs
{
filePicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:allowedUTIs inMode:UIDocumentPickerModeImport];
filePicker.delegate = (id) self;
if( @available(iOS 11.0, *) )
filePicker.allowsMultipleSelection = allowMultipleSelection;
// Show file extensions if possible
if( @available(iOS 13.0, *) )
filePicker.shouldShowFileExtensions = YES;
pickingMultipleFiles = allowMultipleSelection;
filePickerState = 1;
[UnityGetGLViewController() presentViewController:filePicker animated:NO completion:^{ filePickerState = 0; }];
}
+ (void)exportFiles:(NSArray<NSURL *> *)paths
{
if( paths != nil && [paths count] > 0 )
{
if ([paths count] > 1 && [self canPickMultipleFiles] == 1)
filePicker = [[UIDocumentPickerViewController alloc] initWithURLs:paths inMode:UIDocumentPickerModeExportToService];
else
filePicker = [[UIDocumentPickerViewController alloc] initWithURL:paths[0] inMode:UIDocumentPickerModeExportToService];
filePicker.delegate = (id) self;
// Show file extensions if possible
if( @available(iOS 13.0, *) )
filePicker.shouldShowFileExtensions = YES;
filePickerState = 1;
[UnityGetGLViewController() presentViewController:filePicker animated:NO completion:^{ filePickerState = 0; }];
}
}
+ (int)canPickMultipleFiles
{
if( @available(iOS 11.0, *) )
return 1;
return 0;
}
+ (int)isFilePickerBusy
{
if( filePickerState == 2 )
return 1;
if( filePicker != nil )
{
if( filePickerState == 1 || [filePicker presentingViewController] == UnityGetGLViewController() )
return 1;
else
{
filePicker = nil;
return 0;
}
}
else
return 0;
}
// Credit: https://lists.apple.com/archives/cocoa-dev/2012/Jan/msg00052.html
+ (char *)convertExtensionToUTI:(NSString *)extension
{
CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, (__bridge CFStringRef) extension, NULL );
char *result = [self getCString:(__bridge NSString *) fileUTI];
CFRelease( fileUTI );
return result;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{
[self documentPickerCompleted:controller documents:@[url]];
}
#pragma clang diagnostic pop
+ (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
[self documentPickerCompleted:controller documents:urls];
}
+ (void)documentPickerCompleted:(UIDocumentPickerViewController *)controller documents:(NSArray<NSURL *> *)urls
{
filePicker = nil;
filePickerState = 2;
if( controller.documentPickerMode == UIDocumentPickerModeImport )
{
if( !pickingMultipleFiles || [urls count] <= 1 )
{
const char* filePath;
if( [urls count] == 0 )
filePath = "";
else
filePath = [self getCString:[urls[0] path]];
if( pickingMultipleFiles )
UnitySendMessage( "FPResultCallbackiOS", "OnMultipleFilesPicked", filePath );
else
UnitySendMessage( "FPResultCallbackiOS", "OnFilePicked", filePath );
}
else
{
NSMutableArray<NSString *> *filePaths = [NSMutableArray arrayWithCapacity:[urls count]];
for( int i = 0; i < [urls count]; i++ )
[filePaths addObject:[urls[i] path]];
UnitySendMessage( "FPResultCallbackiOS", "OnMultipleFilesPicked", [self getCString:[filePaths componentsJoinedByString:@">"]] );
}
}
else if( controller.documentPickerMode == UIDocumentPickerModeExportToService )
{
if( [urls count] > 0 )
UnitySendMessage( "FPResultCallbackiOS", "OnFilesExported", "1" );
else
UnitySendMessage( "FPResultCallbackiOS", "OnFilesExported", "0" );
}
[controller dismissViewControllerAnimated:NO completion:nil];
}
+ (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
{
filePicker = nil;
UnitySendMessage( "FPResultCallbackiOS", "OnOperationCancelled", "" );
[controller dismissViewControllerAnimated:NO completion:nil];
}
// Credit: https://stackoverflow.com/a/37052118/2373034
+ (char *)getCString:(NSString *)source
{
if( source == nil )
source = @"";
const char *sourceUTF8 = [source UTF8String];
char *result = (char*) malloc( strlen( sourceUTF8 ) + 1 );
strcpy(result, sourceUTF8);
return result;
}
@end
extern "C" void _NativeFilePicker_PickFile( const char* UTIs[], int UTIsCount )
{
NSMutableArray<NSString *> *allowedUTIs = [NSMutableArray arrayWithCapacity:UTIsCount];
for( int i = 0; i < UTIsCount; i++ )
[allowedUTIs addObject:[NSString stringWithUTF8String:UTIs[i]]];
[UNativeFilePicker pickFiles:NO withUTIs:allowedUTIs];
}
extern "C" void _NativeFilePicker_PickMultipleFiles( const char* UTIs[], int UTIsCount )
{
NSMutableArray<NSString *> *allowedUTIs = [NSMutableArray arrayWithCapacity:UTIsCount];
for( int i = 0; i < UTIsCount; i++ )
[allowedUTIs addObject:[NSString stringWithUTF8String:UTIs[i]]];
[UNativeFilePicker pickFiles:YES withUTIs:allowedUTIs];
}
extern "C" void _NativeFilePicker_ExportFiles( const char* files[], int filesCount )
{
NSMutableArray<NSURL *> *paths = [NSMutableArray arrayWithCapacity:filesCount];
for( int i = 0; i < filesCount; i++ )
{
NSString *filePath = [NSString stringWithUTF8String:files[i]];
[paths addObject:[NSURL fileURLWithPath:filePath]];
}
[UNativeFilePicker exportFiles:paths];
}
extern "C" int _NativeFilePicker_CanPickMultipleFiles()
{
return [UNativeFilePicker canPickMultipleFiles];
}
extern "C" int _NativeFilePicker_IsFilePickerBusy()
{
return [UNativeFilePicker isFilePickerBusy];
}
extern "C" char* _NativeFilePicker_ConvertExtensionToUTI( const char* extension )
{
return [UNativeFilePicker convertExtensionToUTI:[NSString stringWithUTF8String:extension]];
}
@@ -0,0 +1,40 @@
fileFormatVersion: 2
guid: 4c65eb8688946be4ea1af62f377f8891
timeCreated: 1498722774
licenseType: Store
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
data:
first:
iPhone: iOS
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 173238
packageName: Native File Picker for Android & iOS
packageVersion: 1.4.1
assetPath: Assets/Plugins/NativeFilePicker/iOS/NativeFilePicker.mm
uploadId: 757770