Развитие системы растений
This commit is contained in:
1 parent
1b578067f3
commit
ba49ef5015
218 files changed
+70415
-1519
No files matched your search
@@ -15,7 +15,7 @@ MonoBehaviour:
|
||||
m_DefaultGroup: 61413ea24ac5d754f9bfe1eff712957a
|
||||
m_currentHash:
|
||||
serializedVersion: 2
|
||||
Hash: cbf960f9f88de008b8321897911b184c
|
||||
Hash: 90049ca0b13b395f1730f0a26f18f452
|
||||
m_OptimizeCatalogSize: 0
|
||||
m_BuildRemoteCatalog: 0
|
||||
m_CatalogRequestsTimeout: 0
|
||||
@@ -24,11 +24,11 @@ MonoBehaviour:
|
||||
m_InternalBundleIdMode: 1
|
||||
m_AssetLoadMode: 0
|
||||
m_BundledAssetProviderType:
|
||||
m_AssemblyName:
|
||||
m_ClassName:
|
||||
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
|
||||
m_AssetBundleProviderType:
|
||||
m_AssemblyName:
|
||||
m_ClassName:
|
||||
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
|
||||
m_IgnoreUnsupportedFilesInBuild: 0
|
||||
m_UniqueBundleIds: 0
|
||||
m_EnableJsonCatalog: 0
|
||||
@@ -106,6 +106,7 @@ MonoBehaviour:
|
||||
- UIPanelConfig
|
||||
- SceneConfig
|
||||
- LayerMaskConfig
|
||||
- ConstantImageConfig
|
||||
m_SchemaTemplates: []
|
||||
m_GroupTemplateObjects:
|
||||
- {fileID: 11400000, guid: 73eda305665711b4cafd86da588a0749, type: 2}
|
||||
|
||||
@@ -15,6 +15,12 @@ MonoBehaviour:
|
||||
m_GroupName: Default Local Group
|
||||
m_GUID: 61413ea24ac5d754f9bfe1eff712957a
|
||||
m_SerializeEntries:
|
||||
- m_GUID: 057464e7ebb719a498ba74eaf05d79c4
|
||||
m_Address: Assets/Resources_moved/Configs/UI/LogoPanel.asset
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels:
|
||||
- UIPanelConfig
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 17c5e56eb6c99db40b6093c9d543bb4f
|
||||
m_Address: Assets/Resources_moved/Configs/UI/CanvasSettingsConfig.asset
|
||||
m_ReadOnly: 0
|
||||
@@ -30,17 +36,44 @@ MonoBehaviour:
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 34c615d91f8c753d1af79f0bc394d317
|
||||
m_Address: Assets/Resources_moved/UI/PlantAdd.prefab
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 4a6908602eab38258aeb79ae3c499a8a
|
||||
m_Address: Assets/Resources_moved/UI/Loading.prefab
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 4bf97e6c7f046f849912182e4ded6c9e
|
||||
m_Address: Assets/Resources_moved/Configs/Audio/Settings/Full3D.asset
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels:
|
||||
- AudioSettingsConfig
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 782c95a3b9a7e71eebcf593e1fdc9c30
|
||||
m_Address: Assets/Resources_moved/UI/Logo.prefab
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: d3cf10166e7fbeb3586d613033c78618
|
||||
m_Address: Assets/Resources_moved/Configs/UI/AddPlant.asset
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels:
|
||||
- UIPanelConfig
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: d7dd69cfadda3d6e18ec390724be0d80
|
||||
m_Address: Assets/Resources_moved/Configs/Audio/AudioSourcePrefabConfig.asset
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: dab114367c8e14ccc96c193b6ac77c94
|
||||
m_Address: Assets/Resources_moved/Configs/UI/LoadingPanel.asset
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels:
|
||||
- UIPanelConfig
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: e1fbe5b2798657342b569f723d16c9de
|
||||
m_Address: Assets/Resources_moved/UI/Canvas.prefab
|
||||
m_ReadOnly: 0
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98b04a1dab924594a3bb3f93d863cc02
|
||||
timeCreated: 1772122466
|
||||
@@ -0,0 +1,18 @@
|
||||
using Code.Infrastructure.Generators;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace Code.Calendar.Constants
|
||||
{
|
||||
[Config("Configs/ImageConfig", true)]
|
||||
public class ConstantImage
|
||||
{
|
||||
[ToConfig] public AssetReference image;
|
||||
[ToConfig] public ConstantImageType imageType;
|
||||
}
|
||||
|
||||
public enum ConstantImageType
|
||||
{
|
||||
NoPhoto
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 188200b6891f44d993888cc4c68a3bea
|
||||
timeCreated: 1772122627
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Code.Infrastructure.Configs;
|
||||
using Code.Infrastructure.EventBus;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace Code.Calendar.Constants
|
||||
{
|
||||
// Написать кодрайтер, который будет находить конфиги и добавлять их сюда пока написано само
|
||||
public class ConstantImagesService
|
||||
{
|
||||
private readonly EventBus _eventBus;
|
||||
|
||||
public List<ConstantImageConfig> Configs { get; private set; }
|
||||
|
||||
public ConstantImagesService(EventBus eventBus, IConfigLocator configLocator)
|
||||
{
|
||||
_eventBus = eventBus;
|
||||
|
||||
var label = configLocator.TryGetManyConfigAssetReferences<ConstantImageConfig>();
|
||||
|
||||
LoadImage(label).Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid LoadImage(string imagesLabel)
|
||||
{
|
||||
if ((await Addressables.LoadResourceLocationsAsync(imagesLabel).Task).Count != 0)
|
||||
{
|
||||
var images =
|
||||
Addressables.LoadAssetsAsync<ConstantImageConfig>(imagesLabel, null, Addressables.MergeMode.Union);
|
||||
|
||||
await images.ToUniTask();
|
||||
|
||||
Configs = images.Result.ToList();
|
||||
}
|
||||
|
||||
_eventBus.RaiseEvent<IConstantImagesServiceHandle>(x=> x.ImagesLoaded());
|
||||
}
|
||||
}
|
||||
|
||||
public interface IConstantImagesServiceHandle : IGlobalSubscriber
|
||||
{
|
||||
void ImagesLoaded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e08f396c88804dac9946217cd9474bfa
|
||||
timeCreated: 1772122482
|
||||
@@ -3,19 +3,38 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Code.Calendar.DataStructures
|
||||
{
|
||||
public struct Plant
|
||||
public struct Plant : IEquatable<Plant>
|
||||
{
|
||||
public string Name;
|
||||
public string IconPath;
|
||||
public int Id;
|
||||
public Guid Id;
|
||||
|
||||
public bool Equals(Plant other) => Id == other.Id;
|
||||
|
||||
public override bool Equals(object obj) => obj is Plant other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
public struct Task
|
||||
public struct Task : IEquatable<Task>
|
||||
{
|
||||
public Guid Id;
|
||||
|
||||
public bool Active;
|
||||
|
||||
public string Description;
|
||||
public DateTime Date;
|
||||
|
||||
public bool IsPeriodic;
|
||||
public int PeriodicInterval;
|
||||
public int PlantId;
|
||||
public DateTime PeriodicUntil;
|
||||
|
||||
public Guid PlantId;
|
||||
public List<string> PhotoPaths;
|
||||
|
||||
public bool Equals(Task other) => Id == other.Id;
|
||||
|
||||
public override bool Equals(object obj) => obj is Task other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Code.Infrastructure.EventBus;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.Calendar.DataStructures
|
||||
{
|
||||
public interface IPlantService
|
||||
{
|
||||
void AddPlant(Plant plant);
|
||||
void DeletePlant(Plant plant);
|
||||
Plant GetPlant(Guid plantId);
|
||||
List<Plant> GetPlants();
|
||||
void EditPlant(Guid guid, Plant newPlant);
|
||||
}
|
||||
|
||||
public class PlantService : IDisposable, IPlantService
|
||||
{
|
||||
private readonly EventBus _eventBus;
|
||||
|
||||
private List<Plant> _plants;
|
||||
|
||||
public PlantService(EventBus eventBus)
|
||||
{
|
||||
_eventBus = eventBus;
|
||||
|
||||
LoadJsonTasks().Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid LoadJsonTasks()
|
||||
{
|
||||
var path = Path.Combine(Application.persistentDataPath, "Plants.json");
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader reader = new(path);
|
||||
|
||||
_plants = JsonConvert.DeserializeObject<List<Plant>>(await reader.ReadToEndAsync()) ?? new List<Plant>();
|
||||
}
|
||||
else
|
||||
{
|
||||
await using (File.Create(path)) { }
|
||||
}
|
||||
|
||||
_eventBus.RaiseEvent<IPlantsServiceInitHandle>(x=> x.PlantsLoaded());
|
||||
}
|
||||
|
||||
private bool _inAction, _reUseRequest;
|
||||
|
||||
private async UniTaskVoid SaveJsonTasks()
|
||||
{
|
||||
if (_inAction)
|
||||
{
|
||||
_reUseRequest = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_inAction = true;
|
||||
|
||||
var json = JsonConvert.SerializeObject(_plants, Formatting.Indented);
|
||||
|
||||
await using StreamWriter writer = new(Path.Combine(Application.persistentDataPath, "Plants.json"));
|
||||
|
||||
await writer.WriteAsync(json).AsUniTask();
|
||||
|
||||
_inAction = false;
|
||||
|
||||
if(!_reUseRequest) _eventBus.RaiseEvent<IPlantsServiceInitHandle>(x=> x.PlantsSaved());
|
||||
else SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public void AddPlant(Plant plant)
|
||||
{
|
||||
plant.Id = Guid.NewGuid();
|
||||
|
||||
_plants.Add(plant);
|
||||
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public void EditPlant(Guid guid, Plant newPlant)
|
||||
{
|
||||
_plants[_plants.FindIndex(x => x.Id == guid)] = newPlant;
|
||||
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
public void DeletePlant(Plant plant)
|
||||
{
|
||||
_plants.Remove(plant);
|
||||
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public Plant GetPlant(Guid plantId) => _plants.FirstOrDefault(x => x.Id == plantId);
|
||||
public List<Plant> GetPlants() => _plants;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPlantsServiceInitHandle : IGlobalSubscriber
|
||||
{
|
||||
void PlantsLoaded();
|
||||
void PlantsSaved();
|
||||
}
|
||||
public interface IPlantsServiceHandle : IGlobalSubscriber
|
||||
{
|
||||
void PlantAdded();
|
||||
void PlantRemoved();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb3e195da68845ea872e2eff73582745
|
||||
timeCreated: 1772116279
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Code.Infrastructure.EventBus;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.Calendar.DataStructures
|
||||
{
|
||||
public interface ITaskService
|
||||
{
|
||||
void AddTask(Task task);
|
||||
void DeleteTask(Task task);
|
||||
List<Task> GetByPlant(Guid plantId);
|
||||
List<Task> GetTasks();
|
||||
List<Task> GetByDate(DateTime date);
|
||||
void EditTask(Guid guid, Task newTask);
|
||||
}
|
||||
|
||||
public class TaskService : IDisposable, ITaskService
|
||||
{
|
||||
private readonly EventBus _eventBus;
|
||||
|
||||
private List<Task> _tasks;
|
||||
|
||||
public TaskService(EventBus eventBus)
|
||||
{
|
||||
_eventBus = eventBus;
|
||||
|
||||
LoadJsonTasks().Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid LoadJsonTasks()
|
||||
{
|
||||
var path = Path.Combine(Application.persistentDataPath, "Tasks.json");
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader reader = new(path);
|
||||
|
||||
_tasks = JsonConvert.DeserializeObject<List<Task>>(await reader.ReadToEndAsync()) ?? new List<Task>();
|
||||
}
|
||||
else
|
||||
{
|
||||
await using (File.Create(path)) { }
|
||||
}
|
||||
|
||||
_eventBus.RaiseEvent<ITaskServiceHandle>(x => x.TasksLoaded());
|
||||
}
|
||||
|
||||
private bool _inAction, _reUseRequest;
|
||||
|
||||
private async UniTaskVoid SaveJsonTasks()
|
||||
{
|
||||
if (_inAction)
|
||||
{
|
||||
_reUseRequest = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_inAction = true;
|
||||
|
||||
var json = JsonConvert.SerializeObject(_tasks, Formatting.Indented);
|
||||
|
||||
await using StreamWriter writer = new(Path.Combine(Application.persistentDataPath, "Tasks.json"));
|
||||
|
||||
await writer.WriteAsync(json).AsUniTask();
|
||||
|
||||
_inAction = false;
|
||||
|
||||
if (!_reUseRequest) _eventBus.RaiseEvent<ITaskServiceHandle>(x => x.TasksSaved());
|
||||
else SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public void AddTask(Task task)
|
||||
{
|
||||
_tasks.Add(task);
|
||||
|
||||
task.Id = Guid.NewGuid();
|
||||
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public void EditTask(Guid guid, Task newTask)
|
||||
{
|
||||
_tasks[_tasks.FindIndex(x => x.Id == guid)] = newTask;
|
||||
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public void DeleteTask(Task task)
|
||||
{
|
||||
_tasks.Remove(task);
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
|
||||
public List<Task> GetByPlant(Guid plantId) => _tasks.Where(x => x.PlantId == plantId).ToList();
|
||||
|
||||
public List<Task> GetByDate(DateTime date)
|
||||
{
|
||||
var byDay = _tasks
|
||||
.Where(x => x.Date.Day == date.Day)
|
||||
.ToList();
|
||||
|
||||
var periodicOnes = _tasks
|
||||
.Where(x => x.IsPeriodic && x.Date.Day + new TimeSpan(0, x.PeriodicInterval, 0).Days == date.Day)
|
||||
.ToList();
|
||||
|
||||
byDay.AddRange(periodicOnes);
|
||||
|
||||
return byDay;
|
||||
}
|
||||
|
||||
public List<Task> GetTasks() => _tasks;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SaveJsonTasks().Forget();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITaskServiceHandle : IGlobalSubscriber
|
||||
{
|
||||
void TasksLoaded();
|
||||
void TasksSaved();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0a70b7566234451ad60128860af9517
|
||||
timeCreated: 1772113905
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 073ac04536184a2b9b164161a2f41a61
|
||||
timeCreated: 1772119626
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Code.Infrastructure.UI;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Code.Calendar.Panels
|
||||
{
|
||||
public class LoadingPanel : UIPanel
|
||||
{
|
||||
[SerializeField] private Image loadingScreen;
|
||||
public override void Open(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(loadingScreen, 1f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
|
||||
public override void Close(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(loadingScreen, 0f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4466c13a031a42498f42c15baf25ec03
|
||||
timeCreated: 1772123986
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Code.Infrastructure.UI;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Code.Calendar.Panels
|
||||
{
|
||||
public class LogoPanel : UIPanel
|
||||
{
|
||||
[SerializeField] private Image logo;
|
||||
public override void Open(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(logo, 1f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
|
||||
public override void Close(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(logo, 0f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03f95468478e4025944889f8b4a54798
|
||||
timeCreated: 1772123906
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Code.Infrastructure.UI;
|
||||
|
||||
namespace Code.Calendar.Panels
|
||||
{
|
||||
public class PictureChooseMenu : UIPanel
|
||||
{
|
||||
|
||||
public override void Open(Action callback = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Close(Action callback = null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46e10e555bb5495899af512610cde913
|
||||
timeCreated: 1772119767
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Code.Calendar.Constants;
|
||||
using Code.Calendar.DataStructures;
|
||||
using Code.Infrastructure.UI;
|
||||
using PrimeTween;
|
||||
using Reflex.Attributes;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Code.Calendar.Panels
|
||||
{
|
||||
public class PlantAddPanel : UIPanel
|
||||
{
|
||||
[Inject] private IPlantService _plantService;
|
||||
[SerializeField] private TMP_InputField plantName;
|
||||
[SerializeField] private Image image;
|
||||
|
||||
public void TryGetIcon()
|
||||
{
|
||||
|
||||
}
|
||||
public void TryAddPlant()
|
||||
{
|
||||
Plant newPlant = new()
|
||||
{
|
||||
Name = plantName.text,
|
||||
IconPath = null
|
||||
};
|
||||
_plantService.AddPlant(newPlant);
|
||||
}
|
||||
|
||||
public override void Open(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(image, 1f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
|
||||
public override void Close(Action callback = null)
|
||||
{
|
||||
Tween.Alpha(image, 0f, 1f).OnComplete(target: this, _ => callback?.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b11bfff50fe458c8799204f39f395ce
|
||||
timeCreated: 1772119636
|
||||
@@ -7,10 +7,7 @@ namespace Code.Infrastructure.App
|
||||
{
|
||||
public enum AppStates
|
||||
{
|
||||
Bootstrap,
|
||||
Gameplay,
|
||||
MainMenu,
|
||||
Platformer
|
||||
Bootstrap
|
||||
}
|
||||
|
||||
public class AppStateMachineContext
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Code.Calendar.Constants;
|
||||
using Code.Calendar.DataStructures;
|
||||
using Code.Infrastructure.Audio;
|
||||
using Code.Infrastructure.Configs;
|
||||
using Code.Infrastructure.Input;
|
||||
@@ -8,7 +10,6 @@ using Reflex.Core;
|
||||
using Reflex.Enums;
|
||||
using Reflex.Injectors;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using Resolution = Reflex.Enums.Resolution;
|
||||
|
||||
namespace Code.Infrastructure.App
|
||||
@@ -20,7 +21,10 @@ namespace Code.Infrastructure.App
|
||||
IUIPanelLoaded,
|
||||
IAudioSourcePoolInitializedHandle,
|
||||
IAudioConfigsLoadedHandle,
|
||||
ILayerMasksInitializedHandle
|
||||
ILayerMasksInitializedHandle,
|
||||
ITaskServiceHandle,
|
||||
IPlantsServiceInitHandle,
|
||||
IConstantImagesServiceHandle
|
||||
{
|
||||
public void InstallBindings(ContainerBuilder containerBuilder)
|
||||
{
|
||||
@@ -50,18 +54,29 @@ namespace Code.Infrastructure.App
|
||||
Resolution.Eager);
|
||||
containerBuilder.RegisterType(typeof(AudioService), new[] { typeof(IAudioService) }, Lifetime.Singleton,
|
||||
Resolution.Eager);
|
||||
|
||||
containerBuilder.RegisterType(typeof(TaskService), new[] { typeof(ITaskService) }, Lifetime.Singleton,
|
||||
Resolution.Eager);
|
||||
containerBuilder.RegisterType(typeof(PlantService), new[] { typeof(IPlantService) }, Lifetime.Singleton,
|
||||
Resolution.Eager);
|
||||
|
||||
containerBuilder.RegisterType(typeof(ConstantImagesService), Lifetime.Singleton,
|
||||
Resolution.Eager);
|
||||
}
|
||||
|
||||
private void InitLoadings()
|
||||
{
|
||||
// UIFactory
|
||||
// UIFactory =
|
||||
// Logo
|
||||
// Scene configs
|
||||
// Input service
|
||||
// Audio Factory
|
||||
// Audio Pool
|
||||
// Scene configs =
|
||||
// Input service
|
||||
// Audio Factory =
|
||||
// Audio Pool =
|
||||
// Layer masks
|
||||
_initialLoading = new Loading(7, () => EssentialLoadEnded(_thatRightContainer));
|
||||
// Tasks =
|
||||
// Plants =
|
||||
// CIS =
|
||||
_initialLoading = new Loading(10, () => EssentialLoadEnded(_thatRightContainer));
|
||||
|
||||
}
|
||||
|
||||
@@ -81,9 +96,7 @@ namespace Code.Infrastructure.App
|
||||
private void EssentialLoadEnded(Container container)
|
||||
{
|
||||
container.Resolve<EventBus.EventBus>().Unsubscribe(this);
|
||||
container.Resolve<AppStateMachine>().SwitchState(AppStates.Bootstrap)
|
||||
.AllowStateTransition(AppStates.Gameplay)
|
||||
.AllowStateTransition(AppStates.Platformer);
|
||||
container.Resolve<AppStateMachine>().SwitchState(AppStates.Bootstrap);
|
||||
container.Resolve<AppStateMachineContext>().StateMachine = container.Resolve<AppStateMachine>();
|
||||
}
|
||||
|
||||
@@ -96,8 +109,7 @@ namespace Code.Infrastructure.App
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void UIInitialized()
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
@@ -138,5 +150,26 @@ namespace Code.Infrastructure.App
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
}
|
||||
|
||||
public void TasksLoaded()
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
}
|
||||
|
||||
public void TasksSaved()
|
||||
{ }
|
||||
|
||||
public void PlantsLoaded()
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
}
|
||||
|
||||
public void PlantsSaved()
|
||||
{ }
|
||||
|
||||
public void ImagesLoaded()
|
||||
{
|
||||
_initialLoading.DoProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ namespace Code.Infrastructure.App.States
|
||||
_eventBus.Subscribe(this);
|
||||
|
||||
Context.UIService.TryRemovePanel(UIPanelType.Logo);
|
||||
|
||||
Context.UIService.TryRemovePanel(UIPanelType.Loading);
|
||||
Context.UIService.TryLoadPanel(UIPanelType.PlantAdd);
|
||||
// Context.AudioFactory.LoadAudioClips(AudioNames.UISounds);
|
||||
}
|
||||
|
||||
@@ -33,9 +34,9 @@ namespace Code.Infrastructure.App.States
|
||||
|
||||
public void UILoaded(UIPanelType uiPanelType)
|
||||
{
|
||||
// if (uiPanelType != UIPanelType.DebugSceneChooser) return;
|
||||
//
|
||||
// Context.UIService.OpenPanel(uiPanelType);
|
||||
if (uiPanelType != UIPanelType.PlantAdd) return;
|
||||
|
||||
Context.UIService.OpenPanel(uiPanelType);
|
||||
}
|
||||
public void OnReadyToPlay(AudioNames groupName)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Code.Infrastructure.Audio
|
||||
{
|
||||
_eventBus = eventBus;
|
||||
_label = configLocator.TryGetManyConfigAssetReferences<AudioInfoConfig>();
|
||||
|
||||
|
||||
for (var i = 0; i < typeof(AudioNames).GetEnumValues().Length; i++)
|
||||
{
|
||||
_audioInfos.Add((AudioNames)i, new Dictionary<int, AssetReference>());
|
||||
@@ -38,15 +38,17 @@ namespace Code.Infrastructure.Audio
|
||||
|
||||
LoadConfigs().Forget();
|
||||
}
|
||||
|
||||
|
||||
private async UniTaskVoid LoadConfigs()
|
||||
{
|
||||
_configsHandle = Addressables.LoadAssetsAsync<AudioInfoConfig>(_label);
|
||||
if ((await Addressables.LoadResourceLocationsAsync(_label).Task).Count != 0)
|
||||
{
|
||||
_configsHandle = Addressables.LoadAssetsAsync<AudioInfoConfig>(_label);
|
||||
await _configsHandle.ToUniTask();
|
||||
|
||||
await _configsHandle.ToUniTask();
|
||||
|
||||
foreach (AudioInfoConfig config in _configsHandle.Result)
|
||||
_audioInfos[config.GroupName].Add(config.id, config.fileReference);
|
||||
foreach (var config in _configsHandle.Result)
|
||||
_audioInfos[config.GroupName].Add(config.id, config.fileReference);
|
||||
}
|
||||
|
||||
_eventBus.RaiseEvent<IAudioConfigsLoadedHandle>(ui => ui.AudioConfigsLoaded());
|
||||
}
|
||||
@@ -54,14 +56,15 @@ namespace Code.Infrastructure.Audio
|
||||
public async UniTaskVoid LoadAudioClips(AudioNames groupName)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"Audios loaded {groupName}");
|
||||
|
||||
|
||||
foreach (var audioInfo in _audioInfos[groupName])
|
||||
if (!_clipsHandles[groupName].ContainsKey(audioInfo.Key))
|
||||
_clipsHandles[groupName].Add(audioInfo.Key, Addressables.LoadAssetAsync<AudioClip>(audioInfo.Value));
|
||||
_clipsHandles[groupName]
|
||||
.Add(audioInfo.Key, Addressables.LoadAssetAsync<AudioClip>(audioInfo.Value));
|
||||
else
|
||||
_clipsHandles[groupName][audioInfo.Key] = Addressables.LoadAssetAsync<AudioClip>(audioInfo.Value);
|
||||
|
||||
foreach (var handle in _clipsHandles[groupName])
|
||||
foreach (var handle in _clipsHandles[groupName])
|
||||
await handle.Value.ToUniTask();
|
||||
|
||||
_eventBus.RaiseEvent<IAudioClipsEventHandle>(ui =>
|
||||
@@ -73,11 +76,12 @@ namespace Code.Infrastructure.Audio
|
||||
public void ReleaseAudios(AudioNames groupName)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"Audios unloaded {groupName}");
|
||||
_eventBus.RaiseEvent<IAudioClipsEventHandle>(x=> x.AudioClipsUnLoaded(groupName));
|
||||
|
||||
foreach (var handle in _clipsHandles)
|
||||
_eventBus.RaiseEvent<IAudioClipsEventHandle>(x => x.AudioClipsUnLoaded(groupName));
|
||||
|
||||
foreach (var handle in _clipsHandles)
|
||||
Addressables.Release(handle.Value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var handles in _clipsHandles)
|
||||
@@ -88,7 +92,8 @@ namespace Code.Infrastructure.Audio
|
||||
}
|
||||
}
|
||||
|
||||
_configsHandle.Release();
|
||||
if (_configsHandle.IsValid())
|
||||
_configsHandle.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Code.Infrastructure.Audio
|
||||
{
|
||||
Addressables.Release(_handles);
|
||||
Addressables.Release(_prefabConfigHandle);
|
||||
Addressables.Release(_sourcePrefab);
|
||||
if(_sourcePrefab) Addressables.Release(_sourcePrefab);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,15 +32,21 @@ namespace Code.Infrastructure.Configs
|
||||
}
|
||||
private async UniTaskVoid SetupMasks(IConfigLocator configLocator, EventBus.EventBus eventBus)
|
||||
{
|
||||
var handle =
|
||||
Addressables.LoadAssetsAsync<LayerMasksConfigConfig>(
|
||||
configLocator.TryGetManyConfigAssetReferences<LayerMasksConfigConfig>(), null, Addressables.MergeMode.Union);
|
||||
var label = configLocator.TryGetManyConfigAssetReferences<LayerMasksConfigConfig>();
|
||||
|
||||
if ((await Addressables.LoadResourceLocationsAsync(label).Task).Count != 0)
|
||||
{
|
||||
|
||||
await handle.ToUniTask();
|
||||
var handle =
|
||||
Addressables.LoadAssetsAsync<LayerMasksConfigConfig>(
|
||||
label, null, Addressables.MergeMode.Union);
|
||||
|
||||
SetMasks(handle.Result);
|
||||
await handle.ToUniTask();
|
||||
|
||||
Addressables.Release(handle);
|
||||
SetMasks(handle.Result);
|
||||
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
|
||||
eventBus.RaiseEvent<ILayerMasksInitializedHandle>(x => x.LayerMasksInitialized());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b23da77f3794e41b162b2ba2e392936
|
||||
timeCreated: 1772114910
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Code.Infrastructure.JSON
|
||||
{
|
||||
public class JsonService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c0d29a568964f799b9fda906d71323f
|
||||
timeCreated: 1772115192
|
||||
@@ -5,6 +5,7 @@ using Code.Infrastructure.Configs;
|
||||
using Code.Infrastructure.EventBus;
|
||||
using Code.Infrastructure.Generators;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||
@@ -12,23 +13,24 @@ using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Code.Infrastructure.SceneLoader
|
||||
{
|
||||
[Config("Configs/SceneConfig",true)]
|
||||
[Config("Configs/SceneConfig", true)]
|
||||
public class SceneInfo
|
||||
{
|
||||
[ToConfig] public AssetReference SceneAsset;
|
||||
[ToConfig] public SceneId SceneId;
|
||||
[ToConfig] public SceneId SceneId;
|
||||
}
|
||||
|
||||
public partial class SceneLoader : IDisposable
|
||||
{
|
||||
private Dictionary<SceneId, SceneInfo> _sceneInfos = new();
|
||||
private Dictionary<SceneId, Action> _scenesActions = new();
|
||||
|
||||
|
||||
private EventBus.EventBus _eventBus;
|
||||
|
||||
partial void SetupScenesInfo();
|
||||
|
||||
|
||||
private AsyncOperationHandle<IList<SceneInfoConfig>> _loadHandle;
|
||||
|
||||
|
||||
public SceneLoader(EventBus.EventBus eventBus, IConfigLocator configLocator)
|
||||
{
|
||||
_eventBus = eventBus;
|
||||
@@ -41,16 +43,17 @@ namespace Code.Infrastructure.SceneLoader
|
||||
if (loadMode == LoadSceneMode.Single)
|
||||
for (var i = 0; i < _loadedScenes.Count; i++)
|
||||
UnloadSceneAsync(_loadedScenes.Keys.ToList()[i]).Forget();
|
||||
|
||||
_loadedScenes.Add(id,Addressables.LoadSceneAsync(_sceneInfos[id].SceneAsset, loadMode));
|
||||
|
||||
|
||||
_loadedScenes.Add(id, Addressables.LoadSceneAsync(_sceneInfos[id].SceneAsset, loadMode));
|
||||
|
||||
await _loadedScenes[id];
|
||||
|
||||
_scenesActions[id]();
|
||||
|
||||
_scenesActions[id]();
|
||||
}
|
||||
|
||||
private Dictionary<SceneId, AsyncOperationHandle<SceneInstance>> _loadedScenes = new();
|
||||
public Scene GetScene(SceneId id) => _loadedScenes[id].Result.Scene;
|
||||
|
||||
public async UniTaskVoid UnloadSceneAsync(SceneId id)
|
||||
{
|
||||
await Addressables.UnloadSceneAsync(_loadedScenes[id]);
|
||||
@@ -61,35 +64,41 @@ namespace Code.Infrastructure.SceneLoader
|
||||
|
||||
private async UniTaskVoid LoadConfigs(string configReferences)
|
||||
{
|
||||
_configHandle = Addressables.LoadAssetsAsync<SceneInfoConfig>(configReferences, OnSceneConfigLoaded, Addressables.MergeMode.Union);
|
||||
|
||||
await _configHandle.ToUniTask();
|
||||
|
||||
SetupScenesInfo();
|
||||
_eventBus.RaiseEvent<ISceneConfigsLoadedHandle>(h => h.SceneConfigsLoaded());
|
||||
if ((await Addressables.LoadResourceLocationsAsync(configReferences).Task).Count != 0)
|
||||
{
|
||||
|
||||
_configHandle = Addressables.LoadAssetsAsync<SceneInfoConfig>(configReferences, OnSceneConfigLoaded,
|
||||
Addressables.MergeMode.Union);
|
||||
|
||||
await _configHandle.ToUniTask();
|
||||
|
||||
SetupScenesInfo();
|
||||
}
|
||||
|
||||
_eventBus.RaiseEvent<ISceneConfigsLoadedHandle>(h => h.SceneConfigsLoaded());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void OnSceneConfigLoaded(SceneInfoConfig sceneInfoConfig)
|
||||
{
|
||||
SceneInfo sceneInfo = new();
|
||||
sceneInfo.SceneId = sceneInfoConfig.SceneId;
|
||||
sceneInfo.SceneAsset = sceneInfoConfig.SceneAsset;
|
||||
|
||||
|
||||
_sceneInfos.Add(sceneInfoConfig.SceneId, sceneInfo);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Addressables.Release(_configHandle);
|
||||
if(_configHandle.IsValid()) _configHandle.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface ISceneConfigsLoadedHandle : IGlobalSubscriber
|
||||
{
|
||||
void SceneConfigsLoaded();
|
||||
}
|
||||
|
||||
public interface ILoadedSceneHandle : IGlobalSubscriber
|
||||
{
|
||||
void SceneLoaded(SceneId sceneId);
|
||||
|
||||
@@ -3,6 +3,10 @@ namespace Code.Infrastructure.UI
|
||||
public enum UIPanelType
|
||||
{
|
||||
Logo,
|
||||
Loading
|
||||
Loading,
|
||||
Calendar,
|
||||
Tasks,
|
||||
Plants,
|
||||
PlantAdd
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbefdb800f5c8e4468bf456469caaf61
|
||||
guid: e2f21cd7d33ee2b4a9aa49675b556cb4
|
||||
folderAsset: yes
|
||||
timeCreated: 1596983915
|
||||
timeCreated: 1525098654
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5c273c78eb8ebb47a9c6005d4041d73
|
||||
guid: 5fc918beb0f7cfe49a7f596d5b6b8768
|
||||
folderAsset: yes
|
||||
timeCreated: 1596971200
|
||||
timeCreated: 1525098661
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
+4
-4
@@ -2,16 +2,16 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeFilePickerNamespace
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
public class FPCallbackHelper : MonoBehaviour
|
||||
public class NCCallbackHelper : MonoBehaviour
|
||||
{
|
||||
private bool autoDestroyWithCallback;
|
||||
private Action mainThreadAction = null;
|
||||
|
||||
public static FPCallbackHelper Create( bool autoDestroyWithCallback )
|
||||
public static NCCallbackHelper Create( bool autoDestroyWithCallback )
|
||||
{
|
||||
FPCallbackHelper result = new GameObject( "FPCallbackHelper" ).AddComponent<FPCallbackHelper>();
|
||||
NCCallbackHelper result = new GameObject( "NCCallbackHelper" ).AddComponent<NCCallbackHelper>();
|
||||
result.autoDestroyWithCallback = autoDestroyWithCallback;
|
||||
DontDestroyOnLoad( result.gameObject );
|
||||
return result;
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2bbe0051e738ea4585119c46d863f19
|
||||
timeCreated: 1545147258
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Android/NCCallbackHelper.cs
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,24 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
public class NCCameraCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private readonly NativeCamera.CameraCallback callback;
|
||||
private readonly NCCallbackHelper callbackHelper;
|
||||
|
||||
public NCCameraCallbackAndroid( NativeCamera.CameraCallback callback ) : base( "com.yasirkula.unity.NativeCameraMediaReceiver" )
|
||||
{
|
||||
this.callback = callback;
|
||||
callbackHelper = NCCallbackHelper.Create( true );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cc8df584d2a4344b929a4f13a53723a
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Android/NCCameraCallbackAndroid.cs
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,24 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
public class NCPermissionCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private readonly NativeCamera.PermissionCallback callback;
|
||||
private readonly NCCallbackHelper callbackHelper;
|
||||
|
||||
public NCPermissionCallbackAndroid( NativeCamera.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeCameraPermissionReceiver" )
|
||||
{
|
||||
this.callback = callback;
|
||||
callbackHelper = NCCallbackHelper.Create( true );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnPermissionResult( int result )
|
||||
{
|
||||
callbackHelper.CallOnMainThread( () => callback( (NativeCamera.Permission) result ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f43d316f7eb1cdf4abcf327a1fbc025b
|
||||
timeCreated: 1596973837
|
||||
guid: bafa24bbc8c455f44a2b98dcbe6451bd
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
@@ -12,8 +12,8 @@ MonoImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Android/NCPermissionCallbackAndroid.cs
|
||||
uploadId: 746912
|
||||
Binary file not shown.
+7
-7
@@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ed817dcf924721488f9672995f51cb3
|
||||
timeCreated: 1607368610
|
||||
guid: 284037eba2526f54d9cf51b5d9bffcfa
|
||||
timeCreated: 1569764737
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 2
|
||||
@@ -33,8 +33,8 @@ PluginImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Android/NativeCamera.aar
|
||||
uploadId: 746912
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f387670006405804d9b5c216ebd8ab1f
|
||||
guid: 16fe39fd709533a4eba946790a8e3123
|
||||
folderAsset: yes
|
||||
timeCreated: 1521452097
|
||||
licenseType: Store
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
#if UNITY_IOS
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
#endif
|
||||
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
private const string SAVE_PATH = "ProjectSettings/NativeCamera.json";
|
||||
|
||||
public bool AutomatedSetup = true;
|
||||
public string CameraUsageDescription = "The app requires access to the camera to take pictures or record videos with it.";
|
||||
public string MicrophoneUsageDescription = "The app will capture microphone input in the recorded video.";
|
||||
|
||||
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 Camera", SettingsScope.Project )
|
||||
{
|
||||
guiHandler = ( searchContext ) => PreferencesGUI(),
|
||||
keywords = new System.Collections.Generic.HashSet<string>() { "Native", "Camera", "Android", "iOS" }
|
||||
};
|
||||
}
|
||||
|
||||
public static void PreferencesGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Instance.AutomatedSetup = EditorGUILayout.Toggle( "Automated Setup", Instance.AutomatedSetup );
|
||||
|
||||
EditorGUI.BeginDisabledGroup( !Instance.AutomatedSetup );
|
||||
Instance.CameraUsageDescription = EditorGUILayout.DelayedTextField( "Camera Usage Description", Instance.CameraUsageDescription );
|
||||
Instance.MicrophoneUsageDescription = EditorGUILayout.DelayedTextField( "Microphone Usage Description", Instance.MicrophoneUsageDescription );
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if( EditorGUI.EndChangeCheck() )
|
||||
Instance.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public class NCPostProcessBuild
|
||||
{
|
||||
#if UNITY_IOS
|
||||
[PostProcessBuild]
|
||||
public static void OnPostprocessBuild( BuildTarget target, string buildPath )
|
||||
{
|
||||
if( !Settings.Instance.AutomatedSetup )
|
||||
return;
|
||||
|
||||
if( target == BuildTarget.iOS )
|
||||
{
|
||||
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
|
||||
string plistPath = Path.Combine( buildPath, "Info.plist" );
|
||||
|
||||
PBXProject pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile( pbxProjectPath );
|
||||
|
||||
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "MobileCoreServices.framework", false );
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "ImageIO.framework", false );
|
||||
|
||||
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
|
||||
|
||||
PlistDocument plist = new PlistDocument();
|
||||
plist.ReadFromString( File.ReadAllText( plistPath ) );
|
||||
|
||||
PlistElementDict rootDict = plist.root;
|
||||
if( !string.IsNullOrEmpty( Settings.Instance.CameraUsageDescription ) )
|
||||
rootDict.SetString( "NSCameraUsageDescription", Settings.Instance.CameraUsageDescription );
|
||||
if( !string.IsNullOrEmpty( Settings.Instance.MicrophoneUsageDescription ) )
|
||||
rootDict.SetString( "NSMicrophoneUsageDescription", Settings.Instance.MicrophoneUsageDescription );
|
||||
|
||||
File.WriteAllText( plistPath, plist.WriteToString() );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d466ecf51c239f9419715f6b59348d6c
|
||||
guid: fa3b57e342928704cb910789ae4dde20
|
||||
timeCreated: 1521452119
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
@@ -12,8 +12,8 @@ MonoImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Editor/NCPostProcessBuild.cs
|
||||
uploadId: 746912
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "NativeFilePicker.Editor",
|
||||
"name": "NativeCamera.Editor",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31117d0234af0084b91a7e53b3d9e0a3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/Editor/NativeCamera.Editor.asmdef
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "NativeCamera.Runtime"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b107fd1956cb3e04985108f5ee29e115
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/NativeCamera.Runtime.asmdef
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,563 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine.Networking;
|
||||
#if UNITY_ANDROID || UNITY_IOS
|
||||
using NativeCameraNamespace;
|
||||
#endif
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
public static class NativeCamera
|
||||
{
|
||||
public struct ImageProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly string mimeType;
|
||||
public readonly ImageOrientation orientation;
|
||||
|
||||
public ImageProperties( int width, int height, string mimeType, ImageOrientation orientation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
public struct VideoProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly long duration;
|
||||
public readonly float rotation;
|
||||
|
||||
public VideoProperties( int width, int height, long duration, float rotation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.duration = duration;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
|
||||
public enum Quality { Default = -1, Low = 0, Medium = 1, High = 2 };
|
||||
public enum PreferredCamera { Default = -1, Rear = 0, Front = 1 }
|
||||
|
||||
// EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
|
||||
public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
|
||||
|
||||
public delegate void PermissionCallback( Permission permission );
|
||||
public delegate void CameraCallback( string path );
|
||||
|
||||
#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.NativeCamera" );
|
||||
|
||||
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 _NativeCamera_CheckPermission();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeCamera_RequestPermission();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeCamera_OpenSettings();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeCamera_HasCamera();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeCamera_TakePicture( string imageSavePath, int maxSize, int preferredCamera );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeCamera_RecordVideo( int quality, int maxDuration, int preferredCamera );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeCamera_GetImageProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeCamera_GetVideoProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeCamera_GetVideoThumbnail( string path, string thumbnailSavePath, int maxSize, double captureTimeInSeconds );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeCamera_LoadImageAtPath( string path, string temporaryFilePath, int maxSize );
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
|
||||
private static string m_temporaryImagePath = null;
|
||||
private static string TemporaryImagePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_temporaryImagePath == null )
|
||||
{
|
||||
m_temporaryImagePath = Path.Combine( Application.temporaryCachePath, "tmpImg" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_temporaryImagePath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
private static string m_iOSSelectedImagePath = null;
|
||||
private static string IOSSelectedImagePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_iOSSelectedImagePath == null )
|
||||
{
|
||||
m_iOSSelectedImagePath = Path.Combine( Application.temporaryCachePath, "CameraImg" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_iOSSelectedImagePath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Runtime Permissions
|
||||
public static bool CheckPermission( bool isPicturePermission )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
return AJC.CallStatic<int>( "CheckPermission", Context, isPicturePermission ) == 1;
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return _NativeCamera_CheckPermission() == 1;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void RequestPermissionAsync( PermissionCallback callback, bool isPicturePermission )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
NCPermissionCallbackAndroid nativeCallback = new( callback );
|
||||
AJC.CallStatic( "RequestPermission", Context, nativeCallback, isPicturePermission );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
NCPermissionCallbackiOS.Initialize( callback );
|
||||
_NativeCamera_RequestPermission();
|
||||
#else
|
||||
callback( Permission.Granted );
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Task<Permission> RequestPermissionAsync( bool isPicturePermission )
|
||||
{
|
||||
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
|
||||
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), isPicturePermission );
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static void OpenSettings()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "OpenSettings", Context );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
_NativeCamera_OpenSettings();
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Camera Functions
|
||||
public static void TakePicture( CameraCallback callback, int maxSize = -1, bool saveAsJPEG = true, PreferredCamera preferredCamera = PreferredCamera.Default )
|
||||
{
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted || IsCameraBusy() )
|
||||
{
|
||||
callback?.Invoke( null );
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select image", "", new string[] { "Image files", "png,jpg,jpeg", "All files", "*" } );
|
||||
|
||||
if( callback != null )
|
||||
callback( pickedFile != "" ? pickedFile : null );
|
||||
#elif UNITY_ANDROID
|
||||
AJC.CallStatic( "TakePicture", Context, new NCCameraCallbackAndroid( callback ), (int) preferredCamera );
|
||||
#elif UNITY_IOS
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
NCCameraCallbackiOS.Initialize( callback );
|
||||
_NativeCamera_TakePicture( IOSSelectedImagePath + ( saveAsJPEG ? ".jpeg" : ".png" ), maxSize, (int) preferredCamera );
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}, true );
|
||||
}
|
||||
|
||||
public static void RecordVideo( CameraCallback callback, Quality quality = Quality.Default, int maxDuration = 0, long maxSizeBytes = 0L, PreferredCamera preferredCamera = PreferredCamera.Default )
|
||||
{
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted || IsCameraBusy() )
|
||||
{
|
||||
callback?.Invoke( null );
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select video", "", new string[] { "Video files", "mp4,mov,webm,avi", "All files", "*" } );
|
||||
|
||||
if( callback != null )
|
||||
callback( pickedFile != "" ? pickedFile : null );
|
||||
#elif UNITY_ANDROID
|
||||
AJC.CallStatic( "RecordVideo", Context, new NCCameraCallbackAndroid( callback ), (int) preferredCamera, (int) quality, maxDuration, maxSizeBytes );
|
||||
#elif UNITY_IOS
|
||||
NCCameraCallbackiOS.Initialize( callback );
|
||||
_NativeCamera_RecordVideo( (int) quality, maxDuration, (int) preferredCamera );
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}, false );
|
||||
}
|
||||
|
||||
public static bool DeviceHasCamera()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
return AJC.CallStatic<bool>( "HasCamera", Context );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return _NativeCamera_HasCamera() == 1;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsCameraBusy()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
return NCCameraCallbackiOS.IsBusy;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
|
||||
{
|
||||
if( string.IsNullOrEmpty( imagePath ) )
|
||||
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
|
||||
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string loadPath = AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string loadPath = _NativeCamera_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize );
|
||||
#else
|
||||
string loadPath = imagePath;
|
||||
#endif
|
||||
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
|
||||
|
||||
Texture2D result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
|
||||
|
||||
try
|
||||
{
|
||||
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
|
||||
{
|
||||
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( loadPath != imagePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( loadPath );
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true )
|
||||
{
|
||||
if( string.IsNullOrEmpty( imagePath ) )
|
||||
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
|
||||
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, temporaryImagePath, maxSize ) );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string loadPath = await Task.Run( () => _NativeCamera_LoadImageAtPath( imagePath, temporaryImagePath, maxSize ) );
|
||||
#else
|
||||
string loadPath = imagePath;
|
||||
#endif
|
||||
|
||||
Texture2D result = null;
|
||||
|
||||
using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable ) )
|
||||
{
|
||||
UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
|
||||
while( !asyncOperation.isDone )
|
||||
await Task.Yield();
|
||||
|
||||
if( www.result != UnityWebRequest.Result.Success )
|
||||
Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
|
||||
else
|
||||
result = DownloadHandlerTexture.GetContent( www );
|
||||
}
|
||||
|
||||
if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
|
||||
{
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
|
||||
|
||||
result = new Texture2D( 2, 2, format, true, false );
|
||||
|
||||
try
|
||||
{
|
||||
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
|
||||
{
|
||||
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( loadPath != imagePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( loadPath );
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
|
||||
{
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string thumbnailPath = AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string thumbnailPath = _NativeCamera_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds );
|
||||
#else
|
||||
string thumbnailPath = null;
|
||||
#endif
|
||||
|
||||
if( !string.IsNullOrEmpty( thumbnailPath ) )
|
||||
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
|
||||
{
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, temporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string thumbnailPath = await Task.Run( () => _NativeCamera_GetVideoThumbnail( videoPath, temporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
|
||||
#else
|
||||
string thumbnailPath = null;
|
||||
#endif
|
||||
|
||||
if( !string.IsNullOrEmpty( thumbnailPath ) )
|
||||
return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_ANDROID
|
||||
private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
|
||||
{
|
||||
T result = default( T );
|
||||
bool hasResult = false;
|
||||
|
||||
await Task.Run( () =>
|
||||
{
|
||||
if( AndroidJNI.AttachCurrentThread() != 0 )
|
||||
Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
result = function();
|
||||
hasResult = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AndroidJNI.DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
return hasResult ? result : function();
|
||||
}
|
||||
#endif
|
||||
|
||||
public static ImageProperties GetImageProperties( string imagePath )
|
||||
{
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetImageProperties", Context, imagePath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeCamera_GetImageProperties( imagePath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
string mimeType = null;
|
||||
ImageOrientation orientation = ImageOrientation.Unknown;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
|
||||
mimeType = properties[2].Trim();
|
||||
if( mimeType.Length == 0 )
|
||||
{
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
if( extension == ".png" )
|
||||
mimeType = "image/png";
|
||||
else if( extension == ".jpg" || extension == ".jpeg" )
|
||||
mimeType = "image/jpeg";
|
||||
else if( extension == ".gif" )
|
||||
mimeType = "image/gif";
|
||||
else if( extension == ".bmp" )
|
||||
mimeType = "image/bmp";
|
||||
else
|
||||
mimeType = null;
|
||||
}
|
||||
|
||||
int orientationInt;
|
||||
if( int.TryParse( properties[3].Trim(), out orientationInt ) )
|
||||
orientation = (ImageOrientation) orientationInt;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageProperties( width, height, mimeType, orientation );
|
||||
}
|
||||
|
||||
public static VideoProperties GetVideoProperties( string videoPath )
|
||||
{
|
||||
if( !File.Exists( videoPath ) )
|
||||
throw new FileNotFoundException( "File not found at " + videoPath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetVideoProperties", Context, videoPath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeCamera_GetVideoProperties( videoPath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
long duration = 0L;
|
||||
float rotation = 0f;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
if( !long.TryParse( properties[2].Trim(), out duration ) )
|
||||
duration = 0L;
|
||||
if( !float.TryParse( properties[3].Trim().Replace( ',', '.' ), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation ) )
|
||||
rotation = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
if( rotation == -90f )
|
||||
rotation = 270f;
|
||||
|
||||
return new VideoProperties( width, height, duration, rotation );
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ecdc359fc7b7e448a5aa6433eafcb8b
|
||||
guid: ff758a73b21d4a04aa6f95679b3da605
|
||||
timeCreated: 1498722610
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
@@ -12,8 +12,8 @@ MonoImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/NativeCamera.cs
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,6 @@
|
||||
= Native Camera for Android & iOS (v1.5.0) =
|
||||
|
||||
Documentation: https://github.com/yasirkula/UnityNativeCamera
|
||||
FAQ: https://github.com/yasirkula/UnityNativeCamera#faq
|
||||
Example code: https://github.com/yasirkula/UnityNativeCamera#example-code
|
||||
E-mail: yasirkula@gmail.com
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a88d1b1b9d7b904b862304c20ed4db4
|
||||
timeCreated: 1563308465
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/README.txt
|
||||
uploadId: 746912
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d99a20b76631e440a062e4fad822fc8
|
||||
guid: 5576acd2a06eb72409e6ec4b7f204a4e
|
||||
folderAsset: yes
|
||||
timeCreated: 1592132993
|
||||
timeCreated: 1498722622
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,73 @@
|
||||
#if UNITY_EDITOR || UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
public class NCCameraCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NCCameraCallbackiOS instance;
|
||||
private NativeCamera.CameraCallback callback;
|
||||
|
||||
private float nextBusyCheckTime;
|
||||
|
||||
public static bool IsBusy { get; private set; }
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeCamera_IsCameraBusy();
|
||||
|
||||
public static void Initialize( NativeCamera.CameraCallback callback )
|
||||
{
|
||||
if( IsBusy )
|
||||
return;
|
||||
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NCCameraCallbackiOS" ).AddComponent<NCCameraCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
|
||||
instance.callback = callback;
|
||||
|
||||
instance.nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
IsBusy = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if( IsBusy )
|
||||
{
|
||||
if( Time.realtimeSinceStartup >= nextBusyCheckTime )
|
||||
{
|
||||
nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
|
||||
if( _NativeCamera_IsCameraBusy() == 0 )
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
NativeCamera.CameraCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( null );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
if( string.IsNullOrEmpty( path ) )
|
||||
path = null;
|
||||
|
||||
NativeCamera.CameraCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( path );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8f19d5713752dc41bd377562677d8ee
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/iOS/NCCameraCallbackiOS.cs
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,35 @@
|
||||
#if UNITY_EDITOR || UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeCameraNamespace
|
||||
{
|
||||
public class NCPermissionCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NCPermissionCallbackiOS instance;
|
||||
private NativeCamera.PermissionCallback callback;
|
||||
|
||||
public static void Initialize( NativeCamera.PermissionCallback callback )
|
||||
{
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NCPermissionCallbackiOS" ).AddComponent<NCPermissionCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
else if( instance.callback != null )
|
||||
instance.callback( NativeCamera.Permission.ShouldAsk );
|
||||
|
||||
instance.callback = callback;
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnPermissionRequested( string message )
|
||||
{
|
||||
NativeCamera.PermissionCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( (NativeCamera.Permission) int.Parse( message ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b11d2999e16ca864f8974988d73c2017
|
||||
guid: 1efd0cf9fb7457142b76fb1bc8672e8d
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
@@ -12,8 +12,8 @@ MonoImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/iOS/NCPermissionCallbackiOS.cs
|
||||
uploadId: 746912
|
||||
@@ -0,0 +1,561 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern UIViewController* UnityGetGLViewController();
|
||||
|
||||
@interface UNativeCamera:NSObject
|
||||
+ (int)checkPermission;
|
||||
+ (void)requestPermission;
|
||||
+ (void)openSettings;
|
||||
+ (int)hasCamera;
|
||||
+ (void)openCamera:(BOOL)imageMode defaultCamera:(int)defaultCamera savePath:(NSString *)imageSavePath maxImageSize:(int)maxImageSize videoQuality:(int)videoQuality maxVideoDuration:(int)maxVideoDuration;
|
||||
+ (int)isCameraBusy;
|
||||
+ (char *)getImageProperties:(NSString *)path;
|
||||
+ (char *)getVideoProperties:(NSString *)path;
|
||||
+ (char *)getVideoThumbnail:(NSString *)path savePath:(NSString *)savePath maximumSize:(int)maximumSize captureTime:(double)captureTime;
|
||||
+ (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize;
|
||||
@end
|
||||
|
||||
@implementation UNativeCamera
|
||||
|
||||
static NSString *pickedMediaSavePath;
|
||||
static UIImagePickerController *imagePicker;
|
||||
static int cameraMaxImageSize = -1;
|
||||
static int imagePickerState = 0; // 0 -> none, 1 -> showing, 2 -> finished
|
||||
static BOOL recordingVideo = NO;
|
||||
static AVAudioSessionCategory unityAudioSessionCategory = AVAudioSessionCategoryAmbient;
|
||||
static NSUInteger unityAudioSessionCategoryOptions = 1;
|
||||
static AVAudioSessionMode unityAudioSessionMode = AVAudioSessionModeDefault;
|
||||
|
||||
// Credit: https://stackoverflow.com/a/20464727/2373034
|
||||
+ (int)checkPermission
|
||||
{
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if( status == AVAuthorizationStatusAuthorized )
|
||||
return 1;
|
||||
else if( status == AVAuthorizationStatusNotDetermined )
|
||||
return 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/20464727/2373034
|
||||
+ (void)requestPermission
|
||||
{
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if( status == AVAuthorizationStatusAuthorized )
|
||||
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", "1" );
|
||||
else if( status == AVAuthorizationStatusNotDetermined )
|
||||
{
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
|
||||
{
|
||||
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", granted ? "1" : "0" );
|
||||
}];
|
||||
}
|
||||
else
|
||||
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", "0" );
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/25453667/2373034
|
||||
+ (void)openSettings
|
||||
{
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
|
||||
}
|
||||
|
||||
+ (int)hasCamera
|
||||
{
|
||||
return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] ? 1 : 0;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/10531752/2373034
|
||||
+ (void)openCamera:(BOOL)imageMode defaultCamera:(int)defaultCamera savePath:(NSString *)imageSavePath maxImageSize:(int)maxImageSize videoQuality:(int)videoQuality maxVideoDuration:(int)maxVideoDuration
|
||||
{
|
||||
if( ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] )
|
||||
{
|
||||
NSLog( @"Device has no registered cameras!" );
|
||||
|
||||
UnitySendMessage( "NCCameraCallbackiOS", "OnMediaReceived", "" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( ( imageMode && ![[UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera] containsObject:(NSString*)kUTTypeImage] ) ||
|
||||
( !imageMode && ![[UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera] containsObject:(NSString*)kUTTypeMovie] ) )
|
||||
{
|
||||
NSLog( @"Camera does not support this operation!" );
|
||||
|
||||
UnitySendMessage( "NCCameraCallbackiOS", "OnMediaReceived", "" );
|
||||
return;
|
||||
}
|
||||
|
||||
imagePicker = [[UIImagePickerController alloc] init];
|
||||
imagePicker.delegate = (id) self;
|
||||
imagePicker.allowsEditing = NO;
|
||||
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
||||
|
||||
if( imageMode )
|
||||
imagePicker.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage];
|
||||
else
|
||||
{
|
||||
imagePicker.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeMovie];
|
||||
|
||||
if( maxVideoDuration > 0 )
|
||||
imagePicker.videoMaximumDuration = maxVideoDuration;
|
||||
|
||||
if( videoQuality == 0 )
|
||||
imagePicker.videoQuality = UIImagePickerControllerQualityTypeLow;
|
||||
else if( videoQuality == 1 )
|
||||
imagePicker.videoQuality = UIImagePickerControllerQualityTypeMedium;
|
||||
else if( videoQuality == 2 )
|
||||
imagePicker.videoQuality = UIImagePickerControllerQualityTypeHigh;
|
||||
}
|
||||
|
||||
if( defaultCamera == 0 && [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear] )
|
||||
imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
|
||||
else if( defaultCamera == 1 && [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront] )
|
||||
imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
|
||||
|
||||
// Bugfix for https://github.com/yasirkula/UnityNativeCamera/issues/45
|
||||
if( !imageMode )
|
||||
{
|
||||
unityAudioSessionCategory = [[AVAudioSession sharedInstance] category];
|
||||
unityAudioSessionCategoryOptions = [[AVAudioSession sharedInstance] categoryOptions];
|
||||
unityAudioSessionMode = [[AVAudioSession sharedInstance] mode];
|
||||
}
|
||||
|
||||
recordingVideo = !imageMode;
|
||||
pickedMediaSavePath = imageSavePath;
|
||||
cameraMaxImageSize = maxImageSize;
|
||||
|
||||
imagePickerState = 1;
|
||||
[UnityGetGLViewController() presentViewController:imagePicker animated:YES completion:^{ imagePickerState = 0; }];
|
||||
}
|
||||
|
||||
+ (int)isCameraBusy
|
||||
{
|
||||
if( imagePickerState == 2 )
|
||||
return 1;
|
||||
|
||||
if( imagePicker != nil )
|
||||
{
|
||||
if( imagePickerState == 1 || [imagePicker presentingViewController] == UnityGetGLViewController() )
|
||||
return 1;
|
||||
|
||||
imagePicker = nil;
|
||||
[self restoreAudioSession];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
|
||||
{
|
||||
NSString *path = nil;
|
||||
if( [info[UIImagePickerControllerMediaType] isEqualToString:(NSString *)kUTTypeImage] )
|
||||
{
|
||||
NSLog( @"UIImagePickerController finished taking picture" );
|
||||
|
||||
UIImage *image = info[UIImagePickerControllerEditedImage] ?: info[UIImagePickerControllerOriginalImage];
|
||||
if( image == nil )
|
||||
path = nil;
|
||||
else
|
||||
{
|
||||
NSString *extension = [pickedMediaSavePath pathExtension];
|
||||
BOOL saveAsJPEG = [extension caseInsensitiveCompare:@"jpg"] == NSOrderedSame || [extension caseInsensitiveCompare:@"jpeg"] == NSOrderedSame;
|
||||
|
||||
// Try to save the image with metadata
|
||||
// CANCELED: a number of users reported that this method results in 90-degree rotated images, uncomment at your own risk
|
||||
// Credit: https://stackoverflow.com/a/15858955
|
||||
/*NSDictionary *metadata = [info objectForKey:UIImagePickerControllerMediaMetadata];
|
||||
NSMutableDictionary *mutableMetadata = nil;
|
||||
CFDictionaryRef metadataRef;
|
||||
CFStringRef imageType;
|
||||
|
||||
if( saveAsJPEG )
|
||||
{
|
||||
mutableMetadata = [metadata mutableCopy];
|
||||
[mutableMetadata setObject:@(1.0) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];
|
||||
|
||||
metadataRef = (__bridge CFDictionaryRef) mutableMetadata;
|
||||
imageType = kUTTypeJPEG;
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataRef = (__bridge CFDictionaryRef) metadata;
|
||||
imageType = kUTTypePNG;
|
||||
}
|
||||
|
||||
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithURL( (__bridge CFURLRef) [NSURL fileURLWithPath:pickedMediaSavePath], imageType , 1, NULL );
|
||||
if( imageDestination == NULL )
|
||||
NSLog( @"Failed to create image destination" );
|
||||
else
|
||||
{
|
||||
CGImageDestinationAddImage( imageDestination, image.CGImage, metadataRef );
|
||||
if( CGImageDestinationFinalize( imageDestination ) )
|
||||
path = pickedMediaSavePath;
|
||||
else
|
||||
NSLog( @"Failed to finalize the image" );
|
||||
|
||||
CFRelease( imageDestination );
|
||||
}*/
|
||||
|
||||
if( path == nil )
|
||||
{
|
||||
//NSLog( @"Attempting to save the image without metadata as fallback" );
|
||||
|
||||
if( ( saveAsJPEG && [UIImageJPEGRepresentation( [self scaleImage:image maxSize:cameraMaxImageSize], 1.0 ) writeToFile:pickedMediaSavePath atomically:YES] ) ||
|
||||
( !saveAsJPEG && [UIImagePNGRepresentation( [self scaleImage:image maxSize:cameraMaxImageSize] ) writeToFile:pickedMediaSavePath atomically:YES] ) )
|
||||
path = pickedMediaSavePath;
|
||||
else
|
||||
{
|
||||
NSLog( @"Error saving image without metadata" );
|
||||
path = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog( @"UIImagePickerController finished recording video" );
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSURL *mediaUrl = info[UIImagePickerControllerMediaURL] ?: info[UIImagePickerControllerReferenceURL];
|
||||
#pragma clang diagnostic pop
|
||||
if( mediaUrl == nil )
|
||||
path = nil;
|
||||
else
|
||||
path = [mediaUrl path];
|
||||
}
|
||||
|
||||
imagePicker = nil;
|
||||
imagePickerState = 2;
|
||||
UnitySendMessage( "NCCameraCallbackiOS", "OnMediaReceived", [self getCString:path] );
|
||||
|
||||
[picker dismissViewControllerAnimated:NO completion:nil];
|
||||
[self restoreAudioSession];
|
||||
}
|
||||
|
||||
+ (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
||||
{
|
||||
NSLog( @"UIImagePickerController cancelled" );
|
||||
|
||||
imagePicker = nil;
|
||||
UnitySendMessage( "NCCameraCallbackiOS", "OnMediaReceived", "" );
|
||||
|
||||
[picker dismissViewControllerAnimated:NO completion:nil];
|
||||
[self restoreAudioSession];
|
||||
}
|
||||
|
||||
// Bugfix for https://github.com/yasirkula/UnityNativeCamera/issues/45
|
||||
+ (void)restoreAudioSession
|
||||
{
|
||||
if( recordingVideo )
|
||||
{
|
||||
NSError *error = nil;
|
||||
if( ![[AVAudioSession sharedInstance] setCategory:unityAudioSessionCategory mode:unityAudioSessionMode options:unityAudioSessionCategoryOptions error:&error] )
|
||||
{
|
||||
if( error != nil )
|
||||
NSLog( @"Error setting audio session category back to %@ with mode %@ and options %lu: %@", unityAudioSessionCategory, unityAudioSessionMode, (unsigned long) unityAudioSessionCategoryOptions, error );
|
||||
else
|
||||
NSLog( @"Error setting audio session category back to %@ with mode %@ and options %lu", unityAudioSessionCategory, unityAudioSessionMode, (unsigned long) unityAudioSessionCategoryOptions );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/4170099/2373034
|
||||
+ (NSArray *)getImageMetadata:(NSString *)path
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int orientation = -1;
|
||||
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithURL( (__bridge CFURLRef) [NSURL fileURLWithPath:path], nil );
|
||||
if( imageSource != nil )
|
||||
{
|
||||
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:(__bridge NSString *)kCGImageSourceShouldCache];
|
||||
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex( imageSource, 0, (__bridge CFDictionaryRef) options );
|
||||
CFRelease( imageSource );
|
||||
|
||||
CGFloat widthF = 0.0f, heightF = 0.0f;
|
||||
if( imageProperties != nil )
|
||||
{
|
||||
if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyPixelWidth ) )
|
||||
CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyPixelWidth ), kCFNumberCGFloatType, &widthF );
|
||||
|
||||
if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyPixelHeight ) )
|
||||
CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyPixelHeight ), kCFNumberCGFloatType, &heightF );
|
||||
|
||||
if( CFDictionaryContainsKey( imageProperties, kCGImagePropertyOrientation ) )
|
||||
{
|
||||
CFNumberGetValue( (CFNumberRef) CFDictionaryGetValue( imageProperties, kCGImagePropertyOrientation ), kCFNumberIntType, &orientation );
|
||||
|
||||
if( orientation > 4 )
|
||||
{
|
||||
// Landscape image
|
||||
CGFloat temp = widthF;
|
||||
widthF = heightF;
|
||||
heightF = temp;
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease( imageProperties );
|
||||
}
|
||||
|
||||
width = (int) roundf( widthF );
|
||||
height = (int) roundf( heightF );
|
||||
}
|
||||
|
||||
return [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:width], [NSNumber numberWithInt:height], [NSNumber numberWithInt:orientation], nil];
|
||||
}
|
||||
|
||||
+ (char *)getImageProperties:(NSString *)path
|
||||
{
|
||||
NSArray *metadata = [self getImageMetadata:path];
|
||||
|
||||
int orientationUnity;
|
||||
int orientation = [metadata[2] intValue];
|
||||
|
||||
// To understand the magic numbers, see ImageOrientation enum in NativeCamera.cs
|
||||
// and http://sylvana.net/jpegcrop/exif_orientation.html
|
||||
if( orientation == 1 )
|
||||
orientationUnity = 0;
|
||||
else if( orientation == 2 )
|
||||
orientationUnity = 4;
|
||||
else if( orientation == 3 )
|
||||
orientationUnity = 2;
|
||||
else if( orientation == 4 )
|
||||
orientationUnity = 6;
|
||||
else if( orientation == 5 )
|
||||
orientationUnity = 5;
|
||||
else if( orientation == 6 )
|
||||
orientationUnity = 1;
|
||||
else if( orientation == 7 )
|
||||
orientationUnity = 7;
|
||||
else if( orientation == 8 )
|
||||
orientationUnity = 3;
|
||||
else
|
||||
orientationUnity = -1;
|
||||
|
||||
return [self getCString:[NSString stringWithFormat:@"%d>%d> >%d", [metadata[0] intValue], [metadata[1] intValue], orientationUnity]];
|
||||
}
|
||||
|
||||
+ (char *)getVideoProperties:(NSString *)path
|
||||
{
|
||||
CGSize size = CGSizeZero;
|
||||
float rotation = 0;
|
||||
long long duration = 0;
|
||||
|
||||
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
|
||||
if( asset != nil )
|
||||
{
|
||||
duration = (long long) round( CMTimeGetSeconds( [asset duration] ) * 1000 );
|
||||
CGAffineTransform transform = [asset preferredTransform];
|
||||
NSArray<AVAssetTrack *>* videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
|
||||
if( videoTracks != nil && [videoTracks count] > 0 )
|
||||
{
|
||||
size = [[videoTracks objectAtIndex:0] naturalSize];
|
||||
transform = [[videoTracks objectAtIndex:0] preferredTransform];
|
||||
}
|
||||
|
||||
rotation = atan2( transform.b, transform.a ) * ( 180.0 / M_PI );
|
||||
}
|
||||
|
||||
return [self getCString:[NSString stringWithFormat:@"%d>%d>%lld>%f", (int) roundf( size.width ), (int) roundf( size.height ), duration, rotation]];
|
||||
}
|
||||
|
||||
+ (char *)getVideoThumbnail:(NSString *)path savePath:(NSString *)savePath maximumSize:(int)maximumSize captureTime:(double)captureTime
|
||||
{
|
||||
AVAssetImageGenerator *thumbnailGenerator = [[AVAssetImageGenerator alloc] initWithAsset:[[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil]];
|
||||
thumbnailGenerator.appliesPreferredTrackTransform = YES;
|
||||
thumbnailGenerator.maximumSize = CGSizeMake( (CGFloat) maximumSize, (CGFloat) maximumSize );
|
||||
thumbnailGenerator.requestedTimeToleranceBefore = kCMTimeZero;
|
||||
thumbnailGenerator.requestedTimeToleranceAfter = kCMTimeZero;
|
||||
|
||||
if( captureTime < 0.0 )
|
||||
captureTime = 0.0;
|
||||
else
|
||||
{
|
||||
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
|
||||
if( asset != nil )
|
||||
{
|
||||
double videoDuration = CMTimeGetSeconds( [asset duration] );
|
||||
if( videoDuration > 0.0 && captureTime >= videoDuration - 0.1 )
|
||||
{
|
||||
if( captureTime > videoDuration )
|
||||
captureTime = videoDuration;
|
||||
|
||||
thumbnailGenerator.requestedTimeToleranceBefore = CMTimeMakeWithSeconds( 1.0, 600 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
CGImageRef image = [thumbnailGenerator copyCGImageAtTime:CMTimeMakeWithSeconds( captureTime, 600 ) actualTime:nil error:&error];
|
||||
if( image == nil )
|
||||
{
|
||||
if( error != nil )
|
||||
NSLog( @"Error generating video thumbnail: %@", error );
|
||||
else
|
||||
NSLog( @"Error generating video thumbnail..." );
|
||||
|
||||
return [self getCString:@""];
|
||||
}
|
||||
|
||||
UIImage *thumbnail = [[UIImage alloc] initWithCGImage:image];
|
||||
CGImageRelease( image );
|
||||
|
||||
if( ![UIImagePNGRepresentation( thumbnail ) writeToFile:savePath atomically:YES] )
|
||||
{
|
||||
NSLog( @"Error saving thumbnail image" );
|
||||
return [self getCString:@""];
|
||||
}
|
||||
|
||||
return [self getCString:savePath];
|
||||
}
|
||||
|
||||
+ (UIImage *)scaleImage:(UIImage *)image maxSize:(int)maxSize
|
||||
{
|
||||
CGFloat width = image.size.width;
|
||||
CGFloat height = image.size.height;
|
||||
|
||||
UIImageOrientation orientation = image.imageOrientation;
|
||||
if( width <= maxSize && height <= maxSize && orientation != UIImageOrientationDown &&
|
||||
orientation != UIImageOrientationLeft && orientation != UIImageOrientationRight &&
|
||||
orientation != UIImageOrientationLeftMirrored && orientation != UIImageOrientationRightMirrored &&
|
||||
orientation != UIImageOrientationUpMirrored && orientation != UIImageOrientationDownMirrored )
|
||||
return image;
|
||||
|
||||
CGFloat scaleX = 1.0f;
|
||||
CGFloat scaleY = 1.0f;
|
||||
if( width > maxSize )
|
||||
scaleX = maxSize / width;
|
||||
if( height > maxSize )
|
||||
scaleY = maxSize / height;
|
||||
|
||||
// Credit: https://github.com/mbcharbonneau/UIImage-Categories/blob/master/UIImage%2BAlpha.m
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo( image.CGImage );
|
||||
BOOL hasAlpha = alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast;
|
||||
|
||||
CGFloat scaleRatio = scaleX < scaleY ? scaleX : scaleY;
|
||||
CGRect imageRect = CGRectMake( 0, 0, width * scaleRatio, height * scaleRatio );
|
||||
UIGraphicsImageRendererFormat *format = [image imageRendererFormat];
|
||||
format.opaque = !hasAlpha;
|
||||
format.scale = image.scale;
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:imageRect.size format:format];
|
||||
image = [renderer imageWithActions:^( UIGraphicsImageRendererContext* _Nonnull myContext )
|
||||
{
|
||||
[image drawInRect:imageRect];
|
||||
}];
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize
|
||||
{
|
||||
// Check if the image can be loaded by Unity without requiring a conversion to PNG
|
||||
// Credit: https://stackoverflow.com/a/12048937/2373034
|
||||
NSString *extension = [path pathExtension];
|
||||
BOOL conversionNeeded = [extension caseInsensitiveCompare:@"jpg"] != NSOrderedSame && [extension caseInsensitiveCompare:@"jpeg"] != NSOrderedSame && [extension caseInsensitiveCompare:@"png"] != NSOrderedSame;
|
||||
|
||||
if( !conversionNeeded )
|
||||
{
|
||||
// Check if the image needs to be processed at all
|
||||
NSArray *metadata = [self getImageMetadata:path];
|
||||
int orientationInt = [metadata[2] intValue]; // 1: correct orientation, [1,8]: valid orientation range
|
||||
if( orientationInt == 1 && [metadata[0] intValue] <= maximumSize && [metadata[1] intValue] <= maximumSize )
|
||||
return [self getCString:path];
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:path];
|
||||
if( image == nil )
|
||||
return [self getCString:path];
|
||||
|
||||
UIImage *scaledImage = [self scaleImage:image maxSize:maximumSize];
|
||||
if( conversionNeeded || scaledImage != image )
|
||||
{
|
||||
if( ![UIImagePNGRepresentation( scaledImage ) writeToFile:tempFilePath atomically:YES] )
|
||||
{
|
||||
NSLog( @"Error creating scaled image" );
|
||||
return [self getCString:path];
|
||||
}
|
||||
|
||||
return [self getCString:tempFilePath];
|
||||
}
|
||||
else
|
||||
return [self getCString:path];
|
||||
}
|
||||
|
||||
// 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" int _NativeCamera_CheckPermission()
|
||||
{
|
||||
return [UNativeCamera checkPermission];
|
||||
}
|
||||
|
||||
extern "C" void _NativeCamera_RequestPermission()
|
||||
{
|
||||
[UNativeCamera requestPermission];
|
||||
}
|
||||
|
||||
extern "C" void _NativeCamera_OpenSettings()
|
||||
{
|
||||
[UNativeCamera openSettings];
|
||||
}
|
||||
|
||||
extern "C" int _NativeCamera_HasCamera()
|
||||
{
|
||||
return [UNativeCamera hasCamera];
|
||||
}
|
||||
|
||||
extern "C" void _NativeCamera_TakePicture( const char* imageSavePath, int maxSize, int preferredCamera )
|
||||
{
|
||||
[UNativeCamera openCamera:YES defaultCamera:preferredCamera savePath:[NSString stringWithUTF8String:imageSavePath] maxImageSize:maxSize videoQuality:-1 maxVideoDuration:-1];
|
||||
}
|
||||
|
||||
extern "C" void _NativeCamera_RecordVideo( int quality, int maxDuration, int preferredCamera )
|
||||
{
|
||||
[UNativeCamera openCamera:NO defaultCamera:preferredCamera savePath:nil maxImageSize:4096 videoQuality:quality maxVideoDuration:maxDuration];
|
||||
}
|
||||
|
||||
extern "C" int _NativeCamera_IsCameraBusy()
|
||||
{
|
||||
return [UNativeCamera isCameraBusy];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeCamera_GetImageProperties( const char* path )
|
||||
{
|
||||
return [UNativeCamera getImageProperties:[NSString stringWithUTF8String:path]];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeCamera_GetVideoProperties( const char* path )
|
||||
{
|
||||
return [UNativeCamera getVideoProperties:[NSString stringWithUTF8String:path]];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeCamera_GetVideoThumbnail( const char* path, const char* thumbnailSavePath, int maxSize, double captureTimeInSeconds )
|
||||
{
|
||||
return [UNativeCamera getVideoThumbnail:[NSString stringWithUTF8String:path] savePath:[NSString stringWithUTF8String:thumbnailSavePath] maximumSize:maxSize captureTime:captureTimeInSeconds];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeCamera_LoadImageAtPath( const char* path, const char* temporaryFilePath, int maxSize )
|
||||
{
|
||||
return [UNativeCamera loadImageAtPath:[NSString stringWithUTF8String:path] tempFilePath:[NSString stringWithUTF8String:temporaryFilePath] maximumSize:maxSize];
|
||||
}
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c65eb8688946be4ea1af62f377f8891
|
||||
guid: f71ce2f3d3a5dbd46af575e628ed9d6e
|
||||
timeCreated: 1498722774
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
@@ -33,8 +33,8 @@ PluginImporter:
|
||||
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
|
||||
productId: 117802
|
||||
packageName: Native Camera for Android & iOS
|
||||
packageVersion: 1.5.0
|
||||
assetPath: Assets/Plugins/NativeCamera/iOS/NativeCamera.mm
|
||||
uploadId: 746912
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
#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
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -1,71 +0,0 @@
|
||||
#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
|
||||
Binary file not shown.
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,65 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -1,199 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"name": "NativeFilePicker.Runtime"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,411 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
= 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
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,155 +0,0 @@
|
||||
#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
|
||||
@@ -1,213 +0,0 @@
|
||||
#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,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e05ed2bddbccb94e9650efb5742e452
|
||||
folderAsset: yes
|
||||
timeCreated: 1518877529
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a607dcda26e7614f86300c6ca717295
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722617
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGCallbackHelper : MonoBehaviour
|
||||
{
|
||||
private bool autoDestroyWithCallback;
|
||||
private Action mainThreadAction = null;
|
||||
|
||||
public static NGCallbackHelper Create( bool autoDestroyWithCallback )
|
||||
{
|
||||
NGCallbackHelper result = new GameObject( "NGCallbackHelper" ).AddComponent<NGCallbackHelper>();
|
||||
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: 2d517fd0f2f85f24698df2775bee58e9
|
||||
timeCreated: 1544889149
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Android/NGCallbackHelper.cs
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,62 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaReceiveCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private readonly NativeGallery.MediaPickCallback callback;
|
||||
private readonly NativeGallery.MediaPickMultipleCallback callbackMultiple;
|
||||
|
||||
private readonly NGCallbackHelper callbackHelper;
|
||||
|
||||
public NGMediaReceiveCallbackAndroid( NativeGallery.MediaPickCallback callback, NativeGallery.MediaPickMultipleCallback callbackMultiple ) : base( "com.yasirkula.unity.NativeGalleryMediaReceiver" )
|
||||
{
|
||||
this.callback = callback;
|
||||
this.callbackMultiple = callbackMultiple;
|
||||
callbackHelper = NGCallbackHelper.Create( true );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMultipleMediaReceived( 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( () => callbackMultiple( ( result != null && result.Length > 0 ) ? result : null ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c18d702b07a63945968db47201b95c9
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Android/NGMediaReceiveCallbackAndroid.cs
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,24 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGPermissionCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private readonly NativeGallery.PermissionCallback callback;
|
||||
private readonly NGCallbackHelper callbackHelper;
|
||||
|
||||
public NGPermissionCallbackAndroid( NativeGallery.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeGalleryPermissionReceiver" )
|
||||
{
|
||||
this.callback = callback;
|
||||
callbackHelper = NGCallbackHelper.Create( true );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnPermissionResult( int result )
|
||||
{
|
||||
callbackHelper.CallOnMainThread( () => callback( (NativeGallery.Permission) result ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a07afac614af1294d8e72a3c083be028
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Android/NGPermissionCallbackAndroid.cs
|
||||
uploadId: 860494
|
||||
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db4d55e1212537e4baa84cac66eb6645
|
||||
timeCreated: 1569764737
|
||||
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: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Android/NativeGallery.aar
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19fc6b8ce781591438a952d8aa9104f8
|
||||
folderAsset: yes
|
||||
timeCreated: 1521452097
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_IOS
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
#endif
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
private const string SAVE_PATH = "ProjectSettings/NativeGallery.json";
|
||||
|
||||
public bool AutomatedSetup = true;
|
||||
public string PhotoLibraryUsageDescription = "The app requires access to Photos to interact with it.";
|
||||
public string PhotoLibraryAdditionsUsageDescription = "The app requires access to Photos to save media to it.";
|
||||
public bool DontAskLimitedPhotosPermissionAutomaticallyOnIos14 = true; // See: https://mackuba.eu/2020/07/07/photo-library-changes-ios-14/
|
||||
|
||||
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 Gallery", SettingsScope.Project )
|
||||
{
|
||||
guiHandler = ( searchContext ) => PreferencesGUI(),
|
||||
keywords = new System.Collections.Generic.HashSet<string>() { "Native", "Gallery", "Android", "iOS" }
|
||||
};
|
||||
}
|
||||
|
||||
public static void PreferencesGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Instance.AutomatedSetup = EditorGUILayout.Toggle( "Automated Setup", Instance.AutomatedSetup );
|
||||
|
||||
EditorGUI.BeginDisabledGroup( !Instance.AutomatedSetup );
|
||||
Instance.PhotoLibraryUsageDescription = EditorGUILayout.DelayedTextField( "Photo Library Usage Description", Instance.PhotoLibraryUsageDescription );
|
||||
Instance.PhotoLibraryAdditionsUsageDescription = EditorGUILayout.DelayedTextField( "Photo Library Additions Usage Description", Instance.PhotoLibraryAdditionsUsageDescription );
|
||||
Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 = EditorGUILayout.Toggle( new GUIContent( "Don't Ask Limited Photos Permission Automatically", "See: https://mackuba.eu/2020/07/07/photo-library-changes-ios-14/. It's recommended to keep this setting enabled" ), Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 );
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if( EditorGUI.EndChangeCheck() )
|
||||
Instance.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public class NGPostProcessBuild
|
||||
{
|
||||
#if UNITY_IOS
|
||||
[PostProcessBuild( 1 )]
|
||||
public static void OnPostprocessBuild( BuildTarget target, string buildPath )
|
||||
{
|
||||
if( !Settings.Instance.AutomatedSetup )
|
||||
return;
|
||||
|
||||
if( target == BuildTarget.iOS )
|
||||
{
|
||||
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
|
||||
string plistPath = Path.Combine( buildPath, "Info.plist" );
|
||||
|
||||
PBXProject pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile( pbxProjectPath );
|
||||
|
||||
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "PhotosUI.framework", true );
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "Photos.framework", false );
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "MobileCoreServices.framework", false );
|
||||
pbxProject.AddFrameworkToProject( targetGUID, "ImageIO.framework", false );
|
||||
|
||||
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
|
||||
|
||||
PlistDocument plist = new PlistDocument();
|
||||
plist.ReadFromString( File.ReadAllText( plistPath ) );
|
||||
|
||||
PlistElementDict rootDict = plist.root;
|
||||
if( !string.IsNullOrEmpty( Settings.Instance.PhotoLibraryUsageDescription ) )
|
||||
rootDict.SetString( "NSPhotoLibraryUsageDescription", Settings.Instance.PhotoLibraryUsageDescription );
|
||||
if( !string.IsNullOrEmpty( Settings.Instance.PhotoLibraryAdditionsUsageDescription ) )
|
||||
rootDict.SetString( "NSPhotoLibraryAddUsageDescription", Settings.Instance.PhotoLibraryAdditionsUsageDescription );
|
||||
if( Settings.Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 )
|
||||
rootDict.SetBoolean( "PHPhotoLibraryPreventAutomaticLimitedAccessAlert", true );
|
||||
|
||||
File.WriteAllText( plistPath, plist.WriteToString() );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dff1540cf22bfb749a2422f445cf9427
|
||||
timeCreated: 1521452119
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Editor/NGPostProcessBuild.cs
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "NativeGallery.Editor",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dffc8e654f00c545a82d0a5274d51eb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/Editor/NativeGallery.Editor.asmdef
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "NativeGallery.Runtime"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e5063adab271564ba0098a06a8cebda
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/NativeGallery.Runtime.asmdef
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,988 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine.Networking;
|
||||
#if UNITY_ANDROID || UNITY_IOS
|
||||
using NativeGalleryNamespace;
|
||||
#endif
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
public static class NativeGallery
|
||||
{
|
||||
public struct ImageProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly string mimeType;
|
||||
public readonly ImageOrientation orientation;
|
||||
|
||||
public ImageProperties( int width, int height, string mimeType, ImageOrientation orientation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
public struct VideoProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly long duration;
|
||||
public readonly float rotation;
|
||||
|
||||
public VideoProperties( int width, int height, long duration, float rotation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.duration = duration;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PermissionType { Read = 0, Write = 1 };
|
||||
public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
|
||||
|
||||
[Flags]
|
||||
public enum MediaType { Image = 1, Video = 2, Audio = 4 };
|
||||
|
||||
// EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
|
||||
public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
|
||||
|
||||
public delegate void PermissionCallback( Permission permission );
|
||||
public delegate void MediaSaveCallback( bool success, string path );
|
||||
public delegate void MediaPickCallback( string path );
|
||||
public delegate void MediaPickMultipleCallback( string[] paths );
|
||||
|
||||
#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.NativeGallery" );
|
||||
|
||||
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 _NativeGallery_CheckPermission( int readPermission, int permissionFreeMode );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_RequestPermission( int readPermission, int permissionFreeMode );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_ShowLimitedLibraryPicker();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_OpenSettings();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_CanPickMultipleMedia();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_GetMediaTypeFromExtension( string extension );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_ImageWriteToAlbum( string path, string album, int permissionFreeMode );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_VideoWriteToAlbum( string path, string album, int permissionFreeMode );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_PickMedia( string mediaSavePath, int mediaType, int permissionFreeMode, int selectionLimit );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_GetImageProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_GetVideoProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_GetVideoThumbnail( string path, string thumbnailSavePath, int maxSize, double captureTimeInSeconds );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_LoadImageAtPath( string path, string temporaryFilePath, int maxSize );
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
|
||||
private static string m_temporaryImagePath = null;
|
||||
private static string TemporaryImagePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_temporaryImagePath == null )
|
||||
{
|
||||
m_temporaryImagePath = Path.Combine( Application.temporaryCachePath, "tmpImg" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_temporaryImagePath;
|
||||
}
|
||||
}
|
||||
|
||||
private static string m_selectedMediaPath = null;
|
||||
private static string SelectedMediaPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_selectedMediaPath == null )
|
||||
{
|
||||
m_selectedMediaPath = Path.Combine( Application.temporaryCachePath, "pickedMedia" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_selectedMediaPath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Runtime Permissions
|
||||
// PermissionFreeMode was initially planned to be a toggleable setting on iOS but it has its own issues when set to false, so its value is forced to true.
|
||||
// These issues are:
|
||||
// - Presented permission dialog will have a "Select Photos" option on iOS 14+ but clicking it will freeze and eventually crash the app (I'm guessing that
|
||||
// this is caused by how permissions are handled synchronously in NativeGallery)
|
||||
// - While saving images/videos to Photos, iOS 14+ users would see the "Select Photos" option (which is irrelevant in this context, hence confusing) and
|
||||
// the user must grant full Photos access in order to save the image/video to a custom album
|
||||
// The only downside of having PermissionFreeMode = true is that, on iOS 14+, images/videos will be saved to the default Photos album rather than the
|
||||
// provided custom album
|
||||
private const bool PermissionFreeMode = true;
|
||||
|
||||
public static bool CheckPermission( PermissionType permissionType, MediaType mediaTypes )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
return AJC.CallStatic<int>( "CheckPermission", Context, permissionType == PermissionType.Read, (int) mediaTypes ) == 1;
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return ProcessPermission( (Permission) _NativeGallery_CheckPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 ) ) == Permission.Granted;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
NGPermissionCallbackAndroid nativeCallback = new( callback );
|
||||
AJC.CallStatic( "RequestPermission", Context, nativeCallback, permissionType == PermissionType.Read, (int) mediaTypes );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
NGPermissionCallbackiOS.Initialize( ( result ) => callback( ProcessPermission( result ) ) );
|
||||
_NativeGallery_RequestPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 );
|
||||
#else
|
||||
callback( Permission.Granted );
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Task<Permission> RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes )
|
||||
{
|
||||
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
|
||||
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), permissionType, mediaTypes );
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private static Permission ProcessPermission( Permission permission )
|
||||
{
|
||||
// result == 3: LimitedAccess permission on iOS, no need to handle it when PermissionFreeMode is set to true
|
||||
return ( PermissionFreeMode && (int) permission == 3 ) ? Permission.Granted : permission;
|
||||
}
|
||||
|
||||
// This function isn't needed when PermissionFreeMode is set to true
|
||||
private static void TryExtendLimitedAccessPermission()
|
||||
{
|
||||
if( IsMediaPickerBusy() )
|
||||
return;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
_NativeGallery_ShowLimitedLibraryPicker();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void OpenSettings()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "OpenSettings", Context );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
_NativeGallery_OpenSettings();
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Save Functions
|
||||
public static void SaveImageToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( mediaBytes, album, filename, MediaType.Image, callback );
|
||||
}
|
||||
|
||||
public static void SaveImageToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( existingMediaPath, album, filename, MediaType.Image, callback );
|
||||
}
|
||||
|
||||
public static void SaveImageToGallery( Texture2D image, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
if( image == null )
|
||||
throw new ArgumentException( "Parameter 'image' is null!" );
|
||||
|
||||
if( filename.EndsWith( ".jpeg", StringComparison.OrdinalIgnoreCase ) || filename.EndsWith( ".jpg", StringComparison.OrdinalIgnoreCase ) )
|
||||
SaveToGallery( GetTextureBytes( image, true ), album, filename, MediaType.Image, callback );
|
||||
else if( filename.EndsWith( ".png", StringComparison.OrdinalIgnoreCase ) )
|
||||
SaveToGallery( GetTextureBytes( image, false ), album, filename, MediaType.Image, callback );
|
||||
else
|
||||
SaveToGallery( GetTextureBytes( image, false ), album, filename + ".png", MediaType.Image, callback );
|
||||
}
|
||||
|
||||
public static void SaveVideoToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( mediaBytes, album, filename, MediaType.Video, callback );
|
||||
}
|
||||
|
||||
public static void SaveVideoToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( existingMediaPath, album, filename, MediaType.Video, callback );
|
||||
}
|
||||
|
||||
private static void SaveAudioToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( mediaBytes, album, filename, MediaType.Audio, callback );
|
||||
}
|
||||
|
||||
private static void SaveAudioToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
|
||||
{
|
||||
SaveToGallery( existingMediaPath, album, filename, MediaType.Audio, callback );
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Load Functions
|
||||
public static bool CanSelectMultipleFilesFromGallery()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
return AJC.CallStatic<bool>( "CanSelectMultipleMedia" );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return _NativeGallery_CanPickMultipleMedia() == 1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool CanSelectMultipleMediaTypesFromGallery()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return true;
|
||||
#elif UNITY_ANDROID
|
||||
return AJC.CallStatic<bool>( "CanSelectMultipleMediaTypes" );
|
||||
#elif UNITY_IOS
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void GetImageFromGallery( MediaPickCallback callback, string title = "", string mime = "image/*" )
|
||||
{
|
||||
GetMediaFromGallery( callback, MediaType.Image, mime, title );
|
||||
}
|
||||
|
||||
public static void GetVideoFromGallery( MediaPickCallback callback, string title = "", string mime = "video/*" )
|
||||
{
|
||||
GetMediaFromGallery( callback, MediaType.Video, mime, title );
|
||||
}
|
||||
|
||||
public static void GetAudioFromGallery( MediaPickCallback callback, string title = "", string mime = "audio/*" )
|
||||
{
|
||||
GetMediaFromGallery( callback, MediaType.Audio, mime, title );
|
||||
}
|
||||
|
||||
public static void GetMixedMediaFromGallery( MediaPickCallback callback, MediaType mediaTypes, string title = "" )
|
||||
{
|
||||
GetMediaFromGallery( callback, mediaTypes, "*/*", title );
|
||||
}
|
||||
|
||||
public static void GetImagesFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "image/*" )
|
||||
{
|
||||
GetMultipleMediaFromGallery( callback, MediaType.Image, mime, title );
|
||||
}
|
||||
|
||||
public static void GetVideosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "video/*" )
|
||||
{
|
||||
GetMultipleMediaFromGallery( callback, MediaType.Video, mime, title );
|
||||
}
|
||||
|
||||
public static void GetAudiosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "audio/*" )
|
||||
{
|
||||
GetMultipleMediaFromGallery( callback, MediaType.Audio, mime, title );
|
||||
}
|
||||
|
||||
public static void GetMixedMediasFromGallery( MediaPickMultipleCallback callback, MediaType mediaTypes, string title = "" )
|
||||
{
|
||||
GetMultipleMediaFromGallery( callback, mediaTypes, "*/*", title );
|
||||
}
|
||||
|
||||
public static bool IsMediaPickerBusy()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
return NGMediaReceiveCallbackiOS.IsBusy;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MediaType GetMediaTypeOfFile( string path )
|
||||
{
|
||||
if( string.IsNullOrEmpty( path ) )
|
||||
return (MediaType) 0;
|
||||
|
||||
string extension = Path.GetExtension( path );
|
||||
if( string.IsNullOrEmpty( extension ) )
|
||||
return (MediaType) 0;
|
||||
|
||||
if( extension[0] == '.' )
|
||||
{
|
||||
if( extension.Length == 1 )
|
||||
return (MediaType) 0;
|
||||
|
||||
extension = extension.Substring( 1 );
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
extension = extension.ToLowerInvariant();
|
||||
if( extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "gif" || extension == "bmp" || extension == "tiff" )
|
||||
return MediaType.Image;
|
||||
else if( extension == "mp4" || extension == "mov" || extension == "wav" || extension == "avi" )
|
||||
return MediaType.Video;
|
||||
else if( extension == "mp3" || extension == "aac" || extension == "flac" )
|
||||
return MediaType.Audio;
|
||||
|
||||
return (MediaType) 0;
|
||||
#elif UNITY_ANDROID
|
||||
string mime = AJC.CallStatic<string>( "GetMimeTypeFromExtension", extension.ToLowerInvariant() );
|
||||
if( string.IsNullOrEmpty( mime ) )
|
||||
return (MediaType) 0;
|
||||
else if( mime.StartsWith( "image/" ) )
|
||||
return MediaType.Image;
|
||||
else if( mime.StartsWith( "video/" ) )
|
||||
return MediaType.Video;
|
||||
else if( mime.StartsWith( "audio/" ) )
|
||||
return MediaType.Audio;
|
||||
else
|
||||
return (MediaType) 0;
|
||||
#elif UNITY_IOS
|
||||
return (MediaType) _NativeGallery_GetMediaTypeFromExtension( extension.ToLowerInvariant() );
|
||||
#else
|
||||
return (MediaType) 0;
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal Functions
|
||||
private static void SaveToGallery( byte[] mediaBytes, string album, string filename, MediaType mediaType, MediaSaveCallback callback )
|
||||
{
|
||||
if( mediaBytes == null || mediaBytes.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'mediaBytes' is null or empty!" );
|
||||
|
||||
if( album == null || album.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'album' is null or empty!" );
|
||||
|
||||
if( filename == null || filename.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'filename' is null or empty!" );
|
||||
|
||||
if( string.IsNullOrEmpty( Path.GetExtension( filename ) ) )
|
||||
Debug.LogWarning( "'filename' doesn't have an extension, this might result in unexpected behaviour!" );
|
||||
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted )
|
||||
{
|
||||
callback?.Invoke( false, null );
|
||||
return;
|
||||
}
|
||||
|
||||
string path = GetTemporarySavePath( filename );
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log( "SaveToGallery called successfully in the Editor" );
|
||||
#else
|
||||
File.WriteAllBytes( path, mediaBytes );
|
||||
#endif
|
||||
|
||||
SaveToGalleryInternal( path, album, mediaType, callback );
|
||||
}, PermissionType.Write, mediaType );
|
||||
}
|
||||
|
||||
private static void SaveToGallery( string existingMediaPath, string album, string filename, MediaType mediaType, MediaSaveCallback callback )
|
||||
{
|
||||
if( !File.Exists( existingMediaPath ) )
|
||||
throw new FileNotFoundException( "File not found at " + existingMediaPath );
|
||||
|
||||
if( album == null || album.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'album' is null or empty!" );
|
||||
|
||||
if( filename == null || filename.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'filename' is null or empty!" );
|
||||
|
||||
if( string.IsNullOrEmpty( Path.GetExtension( filename ) ) )
|
||||
{
|
||||
string originalExtension = Path.GetExtension( existingMediaPath );
|
||||
if( string.IsNullOrEmpty( originalExtension ) )
|
||||
Debug.LogWarning( "'filename' doesn't have an extension, this might result in unexpected behaviour!" );
|
||||
else
|
||||
filename += originalExtension;
|
||||
}
|
||||
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted )
|
||||
{
|
||||
callback?.Invoke( false, null );
|
||||
return;
|
||||
}
|
||||
|
||||
string path = GetTemporarySavePath( filename );
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log( "SaveToGallery called successfully in the Editor" );
|
||||
#else
|
||||
File.Copy( existingMediaPath, path, true );
|
||||
#endif
|
||||
|
||||
SaveToGalleryInternal( path, album, mediaType, callback );
|
||||
}, PermissionType.Write, mediaType );
|
||||
}
|
||||
|
||||
private static void SaveToGalleryInternal( string path, string album, MediaType mediaType, MediaSaveCallback callback )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string savePath = AJC.CallStatic<string>( "SaveMedia", Context, (int) mediaType, path, album );
|
||||
|
||||
File.Delete( path );
|
||||
|
||||
if( callback != null )
|
||||
callback( !string.IsNullOrEmpty( savePath ), savePath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
if( mediaType == MediaType.Audio )
|
||||
{
|
||||
Debug.LogError( "Saving audio files is not supported on iOS" );
|
||||
|
||||
if( callback != null )
|
||||
callback( false, null );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log( "Saving to Pictures: " + Path.GetFileName( path ) );
|
||||
|
||||
NGMediaSaveCallbackiOS.Initialize( callback );
|
||||
if( mediaType == MediaType.Image )
|
||||
_NativeGallery_ImageWriteToAlbum( path, album, PermissionFreeMode ? 1 : 0 );
|
||||
else if( mediaType == MediaType.Video )
|
||||
_NativeGallery_VideoWriteToAlbum( path, album, PermissionFreeMode ? 1 : 0 );
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( true, null );
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string GetTemporarySavePath( string filename )
|
||||
{
|
||||
string saveDir = Path.Combine( Application.persistentDataPath, "NGallery" );
|
||||
Directory.CreateDirectory( saveDir );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
// Ensure a unique temporary filename on iOS:
|
||||
// iOS internally copies images/videos to Photos directory of the system,
|
||||
// but the process is async. The redundant file is deleted by objective-c code
|
||||
// automatically after the media is saved but while it is being saved, the file
|
||||
// should NOT be overwritten. Therefore, always ensure a unique filename on iOS
|
||||
string path = Path.Combine( saveDir, filename );
|
||||
if( File.Exists( path ) )
|
||||
{
|
||||
int fileIndex = 0;
|
||||
string filenameWithoutExtension = Path.GetFileNameWithoutExtension( filename );
|
||||
string extension = Path.GetExtension( filename );
|
||||
|
||||
do
|
||||
{
|
||||
path = Path.Combine( saveDir, string.Concat( filenameWithoutExtension, ++fileIndex, extension ) );
|
||||
} while( File.Exists( path ) );
|
||||
}
|
||||
|
||||
return path;
|
||||
#else
|
||||
return Path.Combine( saveDir, filename );
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void GetMediaFromGallery( MediaPickCallback callback, MediaType mediaType, string mime, string title )
|
||||
{
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted || IsMediaPickerBusy() )
|
||||
{
|
||||
callback?.Invoke( null );
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
System.Collections.Generic.List<string> editorFilters = new System.Collections.Generic.List<string>( 4 );
|
||||
|
||||
if( ( mediaType & MediaType.Image ) == MediaType.Image )
|
||||
{
|
||||
editorFilters.Add( "Image files" );
|
||||
editorFilters.Add( "png,jpg,jpeg" );
|
||||
}
|
||||
|
||||
if( ( mediaType & MediaType.Video ) == MediaType.Video )
|
||||
{
|
||||
editorFilters.Add( "Video files" );
|
||||
editorFilters.Add( "mp4,mov,webm,avi" );
|
||||
}
|
||||
|
||||
if( ( mediaType & MediaType.Audio ) == MediaType.Audio )
|
||||
{
|
||||
editorFilters.Add( "Audio files" );
|
||||
editorFilters.Add( "mp3,wav,aac,flac" );
|
||||
}
|
||||
|
||||
editorFilters.Add( "All files" );
|
||||
editorFilters.Add( "*" );
|
||||
|
||||
string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select file", "", editorFilters.ToArray() );
|
||||
|
||||
if( callback != null )
|
||||
callback( pickedFile != "" ? pickedFile : null );
|
||||
#elif UNITY_ANDROID
|
||||
AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( callback, null ), (int) mediaType, false, SelectedMediaPath, mime, title );
|
||||
#elif UNITY_IOS
|
||||
if( mediaType == MediaType.Audio )
|
||||
{
|
||||
Debug.LogError( "Picking audio files is not supported on iOS" );
|
||||
|
||||
if( callback != null ) // Selecting audio files is not supported on iOS
|
||||
callback( null );
|
||||
}
|
||||
else
|
||||
{
|
||||
NGMediaReceiveCallbackiOS.Initialize( callback, null );
|
||||
_NativeGallery_PickMedia( SelectedMediaPath, (int) ( mediaType & ~MediaType.Audio ), PermissionFreeMode ? 1 : 0, 1 );
|
||||
}
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}, PermissionType.Read, mediaType );
|
||||
}
|
||||
|
||||
private static void GetMultipleMediaFromGallery( MediaPickMultipleCallback callback, MediaType mediaType, string mime, string title )
|
||||
{
|
||||
RequestPermissionAsync( ( permission ) =>
|
||||
{
|
||||
if( permission != Permission.Granted || IsMediaPickerBusy() )
|
||||
{
|
||||
callback?.Invoke( null );
|
||||
return;
|
||||
}
|
||||
|
||||
if( CanSelectMultipleFilesFromGallery() )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( null, callback ), (int) mediaType, true, SelectedMediaPath, mime, title );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
if( mediaType == MediaType.Audio )
|
||||
{
|
||||
Debug.LogError( "Picking audio files is not supported on iOS" );
|
||||
|
||||
if( callback != null ) // Selecting audio files is not supported on iOS
|
||||
callback( null );
|
||||
}
|
||||
else
|
||||
{
|
||||
NGMediaReceiveCallbackiOS.Initialize( null, callback );
|
||||
_NativeGallery_PickMedia( SelectedMediaPath, (int) ( mediaType & ~MediaType.Audio ), PermissionFreeMode ? 1 : 0, 0 );
|
||||
}
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}
|
||||
else if( callback != null )
|
||||
callback( null );
|
||||
}, PermissionType.Read, mediaType );
|
||||
}
|
||||
|
||||
private static byte[] GetTextureBytes( Texture2D texture, bool isJpeg )
|
||||
{
|
||||
try
|
||||
{
|
||||
return isJpeg ? texture.EncodeToJPG( 100 ) : texture.EncodeToPNG();
|
||||
}
|
||||
catch( UnityException )
|
||||
{
|
||||
return GetTextureBytesFromCopy( texture, isJpeg );
|
||||
}
|
||||
catch( ArgumentException )
|
||||
{
|
||||
return GetTextureBytesFromCopy( texture, isJpeg );
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetTextureBytesFromCopy( Texture2D texture, bool isJpeg )
|
||||
{
|
||||
// Texture is marked as non-readable, create a readable copy and save it instead
|
||||
Debug.LogWarning( "Saving non-readable textures is slower than saving readable textures" );
|
||||
|
||||
Texture2D sourceTexReadable = null;
|
||||
RenderTexture rt = RenderTexture.GetTemporary( texture.width, texture.height );
|
||||
RenderTexture activeRT = RenderTexture.active;
|
||||
|
||||
try
|
||||
{
|
||||
Graphics.Blit( texture, rt );
|
||||
RenderTexture.active = rt;
|
||||
|
||||
sourceTexReadable = new Texture2D( texture.width, texture.height, isJpeg ? TextureFormat.RGB24 : TextureFormat.RGBA32, false );
|
||||
sourceTexReadable.ReadPixels( new Rect( 0, 0, texture.width, texture.height ), 0, 0, false );
|
||||
sourceTexReadable.Apply( false, false );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( sourceTexReadable );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = activeRT;
|
||||
RenderTexture.ReleaseTemporary( rt );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return isJpeg ? sourceTexReadable.EncodeToJPG( 100 ) : sourceTexReadable.EncodeToPNG();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate( sourceTexReadable );
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_ANDROID
|
||||
private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
|
||||
{
|
||||
T result = default( T );
|
||||
bool hasResult = false;
|
||||
|
||||
await Task.Run( () =>
|
||||
{
|
||||
if( AndroidJNI.AttachCurrentThread() != 0 )
|
||||
Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
result = function();
|
||||
hasResult = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AndroidJNI.DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
return hasResult ? result : function();
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
|
||||
{
|
||||
if( string.IsNullOrEmpty( imagePath ) )
|
||||
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
|
||||
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string loadPath = AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string loadPath = _NativeGallery_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize );
|
||||
#else
|
||||
string loadPath = imagePath;
|
||||
#endif
|
||||
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
|
||||
|
||||
Texture2D result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
|
||||
|
||||
try
|
||||
{
|
||||
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
|
||||
{
|
||||
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( loadPath != imagePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( loadPath );
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true )
|
||||
{
|
||||
if( string.IsNullOrEmpty( imagePath ) )
|
||||
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
|
||||
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, temporaryImagePath, maxSize ) );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string loadPath = await Task.Run( () => _NativeGallery_LoadImageAtPath( imagePath, temporaryImagePath, maxSize ) );
|
||||
#else
|
||||
string loadPath = imagePath;
|
||||
#endif
|
||||
|
||||
Texture2D result = null;
|
||||
|
||||
using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable ) )
|
||||
{
|
||||
UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
|
||||
while( !asyncOperation.isDone )
|
||||
await Task.Yield();
|
||||
|
||||
if( www.result != UnityWebRequest.Result.Success )
|
||||
Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
|
||||
else
|
||||
result = DownloadHandlerTexture.GetContent( www );
|
||||
}
|
||||
|
||||
if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
|
||||
{
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
|
||||
|
||||
result = new Texture2D( 2, 2, format, true, false );
|
||||
|
||||
try
|
||||
{
|
||||
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
|
||||
{
|
||||
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( result );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( loadPath != imagePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( loadPath );
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
|
||||
{
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string thumbnailPath = AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string thumbnailPath = _NativeGallery_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds );
|
||||
#else
|
||||
string thumbnailPath = null;
|
||||
#endif
|
||||
|
||||
if( !string.IsNullOrEmpty( thumbnailPath ) )
|
||||
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
|
||||
{
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, temporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
|
||||
string thumbnailPath = await Task.Run( () => _NativeGallery_GetVideoThumbnail( videoPath, temporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
|
||||
#else
|
||||
string thumbnailPath = null;
|
||||
#endif
|
||||
|
||||
if( !string.IsNullOrEmpty( thumbnailPath ) )
|
||||
return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ImageProperties GetImageProperties( string imagePath )
|
||||
{
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetImageProperties", Context, imagePath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeGallery_GetImageProperties( imagePath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
string mimeType = null;
|
||||
ImageOrientation orientation = ImageOrientation.Unknown;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
|
||||
mimeType = properties[2].Trim();
|
||||
if( mimeType.Length == 0 )
|
||||
{
|
||||
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
if( extension == ".png" )
|
||||
mimeType = "image/png";
|
||||
else if( extension == ".jpg" || extension == ".jpeg" )
|
||||
mimeType = "image/jpeg";
|
||||
else if( extension == ".gif" )
|
||||
mimeType = "image/gif";
|
||||
else if( extension == ".bmp" )
|
||||
mimeType = "image/bmp";
|
||||
else
|
||||
mimeType = null;
|
||||
}
|
||||
|
||||
int orientationInt;
|
||||
if( int.TryParse( properties[3].Trim(), out orientationInt ) )
|
||||
orientation = (ImageOrientation) orientationInt;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageProperties( width, height, mimeType, orientation );
|
||||
}
|
||||
|
||||
public static VideoProperties GetVideoProperties( string videoPath )
|
||||
{
|
||||
if( !File.Exists( videoPath ) )
|
||||
throw new FileNotFoundException( "File not found at " + videoPath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetVideoProperties", Context, videoPath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeGallery_GetVideoProperties( videoPath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
long duration = 0L;
|
||||
float rotation = 0f;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
if( !long.TryParse( properties[2].Trim(), out duration ) )
|
||||
duration = 0L;
|
||||
if( !float.TryParse( properties[3].Trim().Replace( ',', '.' ), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation ) )
|
||||
rotation = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
if( rotation == -90f )
|
||||
rotation = 270f;
|
||||
|
||||
return new VideoProperties( width, height, duration, rotation );
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce1403606c3629046a0147d3e705f7cc
|
||||
timeCreated: 1498722610
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/NativeGallery.cs
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,6 @@
|
||||
= Native Gallery for Android & iOS (v1.9.2) =
|
||||
|
||||
Documentation: https://github.com/yasirkula/UnityNativeGallery
|
||||
FAQ: https://github.com/yasirkula/UnityNativeGallery#faq
|
||||
Example code: https://github.com/yasirkula/UnityNativeGallery#example-code
|
||||
E-mail: yasirkula@gmail.com
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be769f45b807c40459e5bafb18e887d6
|
||||
timeCreated: 1563308465
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/README.txt
|
||||
uploadId: 860494
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c623599351a41a4c84c20f73c9d8976
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722622
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
#if UNITY_EDITOR || UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaReceiveCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NGMediaReceiveCallbackiOS instance;
|
||||
|
||||
private NativeGallery.MediaPickCallback callback;
|
||||
private NativeGallery.MediaPickMultipleCallback callbackMultiple;
|
||||
|
||||
private float nextBusyCheckTime;
|
||||
|
||||
public static bool IsBusy { get; private set; }
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_IsMediaPickerBusy();
|
||||
|
||||
public static void Initialize( NativeGallery.MediaPickCallback callback, NativeGallery.MediaPickMultipleCallback callbackMultiple )
|
||||
{
|
||||
if( IsBusy )
|
||||
return;
|
||||
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NGMediaReceiveCallbackiOS" ).AddComponent<NGMediaReceiveCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
|
||||
instance.callback = callback;
|
||||
instance.callbackMultiple = callbackMultiple;
|
||||
|
||||
instance.nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
IsBusy = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if( IsBusy )
|
||||
{
|
||||
if( Time.realtimeSinceStartup >= nextBusyCheckTime )
|
||||
{
|
||||
nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
|
||||
if( _NativeGallery_IsMediaPickerBusy() == 0 )
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
NativeGallery.MediaPickCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
NativeGallery.MediaPickMultipleCallback _callbackMultiple = callbackMultiple;
|
||||
callbackMultiple = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( null );
|
||||
|
||||
if( _callbackMultiple != null )
|
||||
_callbackMultiple( null );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
if( string.IsNullOrEmpty( path ) )
|
||||
path = null;
|
||||
|
||||
NativeGallery.MediaPickCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( path );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMultipleMediaReceived( string paths )
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
string[] _paths = SplitPaths( paths );
|
||||
if( _paths != null && _paths.Length == 0 )
|
||||
_paths = null;
|
||||
|
||||
NativeGallery.MediaPickMultipleCallback _callbackMultiple = callbackMultiple;
|
||||
callbackMultiple = null;
|
||||
|
||||
if( _callbackMultiple != null )
|
||||
_callbackMultiple( _paths );
|
||||
}
|
||||
|
||||
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: 71fb861c149c2d1428544c601e52a33c
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 112630
|
||||
packageName: Native Gallery for Android & iOS
|
||||
packageVersion: 1.9.2
|
||||
assetPath: Assets/Plugins/NativeGallery/iOS/NGMediaReceiveCallbackiOS.cs
|
||||
uploadId: 860494
|
||||
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user