支付上传图片和未处理账单功能
|
|
@ -530,3 +530,13 @@ function M:FG_Get_Diamond_Msg(callback)
|
|||
callback(res)
|
||||
end)
|
||||
end
|
||||
|
||||
-- 设置支付图片
|
||||
function M:FG_Set_Pay_Code(paycode,callback)
|
||||
local _data = {}
|
||||
_data.pay_code = paycode
|
||||
local _client = ControllerManager.WebClient
|
||||
_client:send(Protocol.WEB_SEY_PAY_CODE, _data, function(res)
|
||||
callback(res)
|
||||
end)
|
||||
end
|
||||
|
|
@ -17,9 +17,9 @@ local _LocalConfigAllGame = {
|
|||
-- 90,
|
||||
22,
|
||||
55,
|
||||
66,90
|
||||
,91,93
|
||||
,92
|
||||
66, 90
|
||||
, 91, 93
|
||||
, 92
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +55,7 @@ local function __Login(cmd, _data, callBack)
|
|||
user.group_id = account["groupId"]
|
||||
user.type = account["type"] --为1的时候是代理,为0的时候是普通玩家
|
||||
user.agent = account["mng"]
|
||||
user.pay_code = account.pay_code
|
||||
|
||||
user.real_info = account.real_info
|
||||
user.phone = account.phone
|
||||
|
|
@ -62,6 +63,7 @@ local function __Login(cmd, _data, callBack)
|
|||
user.games = FilterGame(data.games)
|
||||
user.havaPsw = data.havaPassword
|
||||
user.currenIp = data.ip
|
||||
|
||||
if Application.platform == RuntimePlatform.WindowsPlayer or Application.platform == RuntimePlatform.WindowsEditor then
|
||||
--GameApplication.Instance.printLog = true
|
||||
else
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ Protocol = {
|
|||
WEB_LOGIN_Phone = "acc/phone_login",
|
||||
-- 获取自己房卡记录
|
||||
WEB_Get_Diamond_Mssages = "acc/get_messages",
|
||||
-- 设置自己支付图片
|
||||
WEB_SEY_PAY_CODE = "acc/set_pay_code",
|
||||
----index----
|
||||
-- 获取公告
|
||||
WEB_UPDATE_NOTICE = "index/get_notice",
|
||||
|
|
|
|||
|
|
@ -344,6 +344,11 @@ end
|
|||
|
||||
--点击桌子进入游戏
|
||||
function M:ClickJoinRoom(context, room, selectSeat)
|
||||
|
||||
if room.maxPlayers == 2 and (not DataManager.SelfUser.pay_code or #DataManager.SelfUser.pay_code < 6) then
|
||||
ViewUtil:ErrorTip("没有绑定二维码不能进入双人游戏")
|
||||
return
|
||||
end
|
||||
local group = DataManager.CurrenGroup
|
||||
local roomCtr = ControllerManager.GetController(RoomController)
|
||||
roomCtr:PublicJoinRoom(
|
||||
|
|
|
|||
|
|
@ -117,11 +117,40 @@ function M:init(url)
|
|||
local btn_changeInfo = self._view:GetChild('btn_changeInfo')
|
||||
btn_changeInfo:GetController('type').selectedIndex = pswType
|
||||
btn_changeInfo.onClick:Set(function()
|
||||
local passwordUpdateView = PasswordUpdateView.new(pswType, function(res)
|
||||
pswType = DataManager.SelfUser.havaPsw and 1 or 0
|
||||
btn_changeInfo:GetController('type').selectedIndex = pswType
|
||||
-- local passwordUpdateView = PasswordUpdateView.new(pswType, function(res)
|
||||
-- pswType = DataManager.SelfUser.havaPsw and 1 or 0
|
||||
-- btn_changeInfo:GetController('type').selectedIndex = pswType
|
||||
-- end)
|
||||
-- passwordUpdateView:Show()
|
||||
|
||||
GameApplication.Instance:PickAndUploadImage(function(ok, jsonStr)
|
||||
print("PickAndUpload ok=", ok, jsonStr)
|
||||
|
||||
if not ok then
|
||||
local err = json.decode(jsonStr)
|
||||
print("err=", err.err, err.http, err.resp)
|
||||
return
|
||||
end
|
||||
|
||||
local r = json.decode(jsonStr)
|
||||
|
||||
-- 服务器返回在 r.serverResp
|
||||
-- 你的 success 格式: {code=1,msg="ok",data={url=...,fullurl=...}}
|
||||
local srv = r.serverResp
|
||||
local data = srv.data
|
||||
|
||||
print("local=", r.localPath)
|
||||
print("uploaded url=", data.url) --实战用
|
||||
print("uploaded fullurl=", data.fullurl) --测试用
|
||||
|
||||
-- 这里 fullurl 就能直接用于显示/下载/存数据库
|
||||
local loddyCtr1 = ControllerManager.GetController(LoddyController)
|
||||
loddyCtr1:FG_Set_Pay_Code(data.fullurl,function(res)
|
||||
if res.ReturnCode == 0 then
|
||||
|
||||
end
|
||||
end)
|
||||
end)
|
||||
passwordUpdateView:Show()
|
||||
end)
|
||||
|
||||
local loddyCtr1 = ControllerManager.GetController(LoddyController)
|
||||
|
|
|
|||
|
|
@ -154,11 +154,12 @@ function M:InitView(url)
|
|||
self._flag_loadImageSucces = true
|
||||
end)
|
||||
|
||||
-- btn_head.onClick:Set(function()
|
||||
-- local lobbyPlayerInfoView = LobbyPlayerInfoView.new(DataManager.SelfUser, function()
|
||||
-- ImageLoad.Load(DataManager.SelfUser.head_url, btn_head._iconObject)
|
||||
-- end)
|
||||
-- end)
|
||||
--临时开去测试设置支付图片(全部样式没有修改)
|
||||
btn_head.onClick:Set(function()
|
||||
local lobbyPlayerInfoView = LobbyPlayerInfoView.new(DataManager.SelfUser, function()
|
||||
ImageLoad.Load(DataManager.SelfUser.head_url, btn_head._iconObject)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component size="1685,1030">
|
||||
<displayList>
|
||||
<component id="n0_k13d" name="bg" src="ej1ib7jgi" fileName="Bg_Title.xml" xy="0,0" size="1685,1030"/>
|
||||
<component id="n1_k13d" name="btn_close" src="ej1ib7jgf" fileName="buttons/Btn_Normol.xml" xy="1586,34" size="63,63">
|
||||
<Button icon="ui://27vd145bej1ib7jgk"/>
|
||||
</component>
|
||||
<text id="n2_k13d" name="n2" xy="504,376" size="394,42" fontSize="30" text="检测到当前对局为二人对局,"/>
|
||||
</displayList>
|
||||
</component>
|
||||
|
|
@ -2359,6 +2359,7 @@
|
|||
<image id="qekdbt7deg" name="13 - 间.png" path="/font/font_bg2_text1/"/>
|
||||
<image id="qekdbt7deh" name="btn_message.png" path="/images/" exported="true"/>
|
||||
<component id="qekdbt7dei" name="Btn_Head_Round5x.xml" path="/buttons/" exported="true" favorite="true"/>
|
||||
<component id="k13dbt7dej" name="comp_payTips.xml" path="/component/" exported="true"/>
|
||||
</resources>
|
||||
<publish name="Common" path="..\wb_unity_pro\Assets\ART\base\common\ui" packageCount="2" maxAtlasSize="2048" rotation="true">
|
||||
<atlas name="默认" index="0"/>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component size="2142,166" extention="Button">
|
||||
<controller name="isShow" pages="0,,1," selected="1"/>
|
||||
<controller name="isShow" pages="0,,1," selected="0"/>
|
||||
<displayList>
|
||||
<image id="n0_jo5d" name="n0" src="jo5dbt7d95" fileName="FamilyManager/image/bg_menberManger_child.png" xy="-6,-6" size="2156,179"/>
|
||||
<text id="n2_jo5d" name="text_gameName" xy="46,48" size="318,69" font="ui://27vd145bej1ib7jgh" fontSize="52" color="#929292" align="center" autoSize="none" text="红中麻将">
|
||||
<relation target="" sidePair="left-left%,top-top"/>
|
||||
</text>
|
||||
<text id="n3_jo5d" name="text_playName" xy="436,46" size="375,69" fontSize="52" color="#929292" align="center" autoSize="none" text="3元红中麻将">
|
||||
<text id="n3_jo5d" name="text_playName" xy="396,46" size="375,69" fontSize="52" color="#929292" align="center" autoSize="none" text="3元红中麻将">
|
||||
<relation target="" sidePair="left-left%,top-top"/>
|
||||
</text>
|
||||
<text id="n4_jo5d" name="text_time" xy="905,17" size="268,137" fontSize="52" color="#929292" align="center" text="2025-11-11
01:07">
|
||||
<text id="n4_jo5d" name="text_time" xy="805,17" size="268,137" fontSize="52" color="#929292" align="center" text="2025-11-11
01:07">
|
||||
<relation target="" sidePair="left-left%,top-top"/>
|
||||
</text>
|
||||
<component id="n6_jo5d" name="btn_assent" src="l8fnb7jh7" fileName="buttons/Btn_Bg1WithText.xml" pkg="27vd145b" xy="1848,46" size="207,65">
|
||||
<gearDisplay controller="isShow" pages="1"/>
|
||||
<component id="n6_jo5d" name="btn_assent" src="l8fnb7jh7" fileName="buttons/Btn_Bg1WithText.xml" pkg="27vd145b" xy="1808,46" size="207,65">
|
||||
<gearXY controller="isShow" pages="0" values="1808,46" default="1628,46"/>
|
||||
<Button title="确认收款" titleFontSize="36" icon="ui://htcn7v3rjo5dbt7d98"/>
|
||||
</component>
|
||||
<list id="n11_i2hi" name="list" xy="1282,21" size="462,125" layout="row" selectionMode="none" scroll="horizontal" defaultItem="ui://htcn7v3ri2hibt7det" align="center" vAlign="middle" autoClearItems="true">
|
||||
<list id="n11_i2hi" name="list" xy="1089,21" size="462,125" layout="row" selectionMode="none" scroll="horizontal" defaultItem="ui://htcn7v3ri2hibt7det" align="center" vAlign="middle" autoClearItems="true">
|
||||
<item/>
|
||||
<item/>
|
||||
</list>
|
||||
<component id="n12_z4bi" name="btn_show" src="l8fnb7jh7" fileName="buttons/Btn_Bg1WithText.xml" pkg="27vd145b" xy="1883,46" size="207,65">
|
||||
<gearDisplay controller="isShow" pages="1"/>
|
||||
<Button title="查看二维码" titleFontSize="36" icon="ui://htcn7v3rjo5dbt7d98"/>
|
||||
</component>
|
||||
</displayList>
|
||||
<Button/>
|
||||
</component>
|
||||
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 4.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 506 KiB After Width: | Height: | Size: 518 KiB |
|
Before Width: | Height: | Size: 975 KiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 763 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 411 B |
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c30b7448361d11948bf212d4bae1edb7
|
||||
guid: 5e9e9d6ba28c83c47b5120bb03e6a2d1
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e05ed2bddbccb94e9650efb5742e452
|
||||
folderAsset: yes
|
||||
timeCreated: 1518877529
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0a607dcda26e7614f86300c6ca717295
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722617
|
||||
licenseType: Pro
|
||||
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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d517fd0f2f85f24698df2775bee58e9
|
||||
timeCreated: 1544889149
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4c18d702b07a63945968db47201b95c9
|
||||
timeCreated: 1519060539
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a07afac614af1294d8e72a3c083be028
|
||||
timeCreated: 1519060539
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
fileFormatVersion: 2
|
||||
guid: db4d55e1212537e4baa84cac66eb6645
|
||||
timeCreated: 1569764737
|
||||
licenseType: Free
|
||||
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:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 19fc6b8ce781591438a952d8aa9104f8
|
||||
folderAsset: yes
|
||||
timeCreated: 1521452097
|
||||
licenseType: Free
|
||||
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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dff1540cf22bfb749a2422f445cf9427
|
||||
timeCreated: 1521452119
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3dffc8e654f00c545a82d0a5274d51eb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "NativeGallery.Runtime"
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6e5063adab271564ba0098a06a8cebda
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,995 @@
|
|||
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 NGPermissionCallbackAndroid( 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 UNITY_2020_2_OR_NEWER
|
||||
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 );
|
||||
#else
|
||||
if (www.isNetworkError || www.isHttpError)
|
||||
Debug.LogWarning("Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error);
|
||||
else
|
||||
result = DownloadHandlerTexture.GetContent(www);
|
||||
#endif
|
||||
}
|
||||
|
||||
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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ce1403606c3629046a0147d3e705f7cc
|
||||
timeCreated: 1498722610
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be769f45b807c40459e5bafb18e887d6
|
||||
timeCreated: 1563308465
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c623599351a41a4c84c20f73c9d8976
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722622
|
||||
licenseType: Pro
|
||||
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,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 71fb861c149c2d1428544c601e52a33c
|
||||
timeCreated: 1519060539
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#if UNITY_EDITOR || UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaSaveCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NGMediaSaveCallbackiOS instance;
|
||||
private NativeGallery.MediaSaveCallback callback;
|
||||
|
||||
public static void Initialize( NativeGallery.MediaSaveCallback callback )
|
||||
{
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NGMediaSaveCallbackiOS" ).AddComponent<NGMediaSaveCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
else if( instance.callback != null )
|
||||
instance.callback( false, null );
|
||||
|
||||
instance.callback = callback;
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaSaveCompleted( string message )
|
||||
{
|
||||
NativeGallery.MediaSaveCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( true, null );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnMediaSaveFailed( string error )
|
||||
{
|
||||
NativeGallery.MediaSaveCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( false, null );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9cbb865d0913a0d47bb6d2eb3ad04c4f
|
||||
timeCreated: 1519060539
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#if UNITY_EDITOR || UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGPermissionCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NGPermissionCallbackiOS instance;
|
||||
private NativeGallery.PermissionCallback callback;
|
||||
|
||||
public static void Initialize( NativeGallery.PermissionCallback callback )
|
||||
{
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NGPermissionCallbackiOS" ).AddComponent<NGPermissionCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
else if( instance.callback != null )
|
||||
instance.callback( NativeGallery.Permission.ShouldAsk );
|
||||
|
||||
instance.callback = callback;
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnPermissionRequested( string message )
|
||||
{
|
||||
NativeGallery.PermissionCallback _callback = callback;
|
||||
callback = null;
|
||||
|
||||
if( _callback != null )
|
||||
_callback( (NativeGallery.Permission) int.Parse( message ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bc6d7fa0a99114a45b1a6800097c6eb1
|
||||
timeCreated: 1519060539
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 953e0b740eb03144883db35f72cad8a6
|
||||
timeCreated: 1498722774
|
||||
licenseType: Pro
|
||||
PluginImporter:
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
data:
|
||||
first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
data:
|
||||
first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -36,7 +36,12 @@ public class GameApplication : MonoBehaviour
|
|||
ExceptionReporter _ex_reporter=null;
|
||||
private LuaFunction _share_callback;
|
||||
private LuaFunction _wxlogin_callback;
|
||||
|
||||
|
||||
// === Gallery / Pick Image ===
|
||||
private LuaFunction _pick_image_callback;
|
||||
private int _pick_max_size = 1024; // 长边最大像素
|
||||
private int _pick_jpg_quality = 80; // JPG质量 1~100
|
||||
|
||||
void Awake()
|
||||
{
|
||||
TaurusUnity.init();
|
||||
|
|
@ -330,6 +335,7 @@ public class GameApplication : MonoBehaviour
|
|||
}
|
||||
[DllImport("native-lib")]
|
||||
private static extern IntPtr getAppIdCSharp();
|
||||
|
||||
public void WXLogin(LuaFunction callback)
|
||||
{
|
||||
if (!buildApp) return;
|
||||
|
|
@ -557,4 +563,130 @@ public class GameApplication : MonoBehaviour
|
|||
{
|
||||
Application.Quit();
|
||||
}
|
||||
|
||||
private const string UPLOAD_IMAGE_URL = "http://8.138.255.70:9898/api/common/uploadimage";
|
||||
|
||||
/// Lua: GameApplication.Instance:PickAndUploadImage(function(ok, json) end)
|
||||
/// 返回 json:
|
||||
/// - 选图信息: localPath,width,height,mime,orientation
|
||||
/// - 上传结果: url, fullurl, size, ext, serverName
|
||||
public void PickAndUploadImage(LuaFunction cb)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
SafeLuaCallback(cb, false, "{\"err\":\"UNITY_EDITOR\"}");
|
||||
return;
|
||||
#else
|
||||
// 1) 先选图
|
||||
try
|
||||
{
|
||||
NativeGallery.RequestPermissionAsync((perm) =>
|
||||
{
|
||||
if (perm != NativeGallery.Permission.Granted)
|
||||
{
|
||||
SafeLuaCallback(cb, false, "{\"err\":\"PERMISSION_DENIED\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
NativeGallery.GetImageFromGallery((path) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
{
|
||||
SafeLuaCallback(cb, false, "{\"err\":\"CANCEL_OR_NOT_FOUND\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 拿图片属性(可选)
|
||||
NativeGallery.ImageProperties prop;
|
||||
try { prop = NativeGallery.GetImageProperties(path); }
|
||||
catch { prop = new NativeGallery.ImageProperties(0, 0, "", NativeGallery.ImageOrientation.Unknown); }
|
||||
|
||||
// 3) 上传
|
||||
StartCoroutine(CoUploadPickedImage(path, prop, cb));
|
||||
}, "请选择一张图片", "image/*");
|
||||
|
||||
}, NativeGallery.PermissionType.Read, NativeGallery.MediaType.Image);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SafeLuaCallback(cb, false, "{\"err\":\"EXCEPTION\",\"msg\":\"" + EscapeJson(e.ToString()) + "\"}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private IEnumerator CoUploadPickedImage(string localPath, NativeGallery.ImageProperties prop, LuaFunction cb)
|
||||
{
|
||||
byte[] bytes;
|
||||
try { bytes = File.ReadAllBytes(localPath); }
|
||||
catch (Exception e)
|
||||
{
|
||||
SafeLuaCallback(cb, false, "{\"err\":\"READ_FILE_FAIL\",\"msg\":\"" + EscapeJson(e.Message) + "\"}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(localPath);
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(fileName)))
|
||||
fileName += ".jpg"; // 防御:没后缀就补一个
|
||||
|
||||
WWWForm form = new WWWForm();
|
||||
form.AddBinaryData("file", bytes, fileName, "application/octet-stream"); // 服务端字段名是 file
|
||||
|
||||
using (UnityWebRequest req = UnityWebRequest.Post(UPLOAD_IMAGE_URL, form))
|
||||
{
|
||||
req.timeout = 60;
|
||||
yield return req.SendWebRequest();
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
bool ok = req.result == UnityWebRequest.Result.Success;
|
||||
#else
|
||||
bool ok = !(req.isNetworkError || req.isHttpError);
|
||||
#endif
|
||||
string body = req.downloadHandler != null ? req.downloadHandler.text : "";
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
string errJson = "{"
|
||||
+ "\"err\":\"UPLOAD_FAIL\","
|
||||
+ "\"http\":\"" + EscapeJson(req.error ?? "") + "\","
|
||||
+ "\"resp\":\"" + EscapeJson(body) + "\""
|
||||
+ "}";
|
||||
SafeLuaCallback(cb, false, errJson);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 把选图信息 + 服务器返回一起打包给 Lua(不解析服务端 JSON,直接原样塞回,Lua 自己 decode)
|
||||
string resultJson = "{"
|
||||
+ "\"localPath\":\"" + EscapeJson(localPath) + "\","
|
||||
+ "\"width\":" + prop.width + ","
|
||||
+ "\"height\":" + prop.height + ","
|
||||
+ "\"mime\":\"" + EscapeJson(prop.mimeType ?? "") + "\","
|
||||
+ "\"orientation\":" + (int)prop.orientation + ","
|
||||
+ "\"serverResp\":" + SafeJsonObject(body)
|
||||
+ "}";
|
||||
|
||||
SafeLuaCallback(cb, true, resultJson);
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeLuaCallback(LuaFunction fn, bool ok, string json)
|
||||
{
|
||||
if (fn == null) return;
|
||||
try { fn.Call(ok, json); }
|
||||
catch (Exception e) { Debug.LogException(e); }
|
||||
finally { fn.Dispose(); }
|
||||
}
|
||||
|
||||
private string EscapeJson(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "";
|
||||
return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
}
|
||||
|
||||
// 服务端返回一般是 {"code":1,...},这里确保拼接时是合法 json
|
||||
private string SafeJsonObject(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "{}";
|
||||
s = s.Trim();
|
||||
if (s.StartsWith("{") && s.EndsWith("}")) return s; // 看起来像对象
|
||||
return "{\"raw\":\"" + EscapeJson(s) + "\"}";
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@ public class PlatformAndroid :MonoBehaviour
|
|||
return _activity.Call<string>("__getRoomID");
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public class GameApplicationWrap
|
|||
L.RegFunction("GetPublicIP", GetPublicIP);
|
||||
L.RegFunction("InitBaiduMap", InitBaiduMap);
|
||||
L.RegFunction("QuitGameOnUnity", QuitGameOnUnity);
|
||||
L.RegFunction("PickAndUploadImage", PickAndUploadImage);
|
||||
L.RegFunction("__eq", op_Equality);
|
||||
L.RegFunction("__tostring", ToLua.op_ToString);
|
||||
L.RegVar("Instance", get_Instance, set_Instance);
|
||||
|
|
@ -401,6 +402,23 @@ public class GameApplicationWrap
|
|||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
|
||||
static int PickAndUploadImage(IntPtr L)
|
||||
{
|
||||
try
|
||||
{
|
||||
ToLua.CheckArgsCount(L, 2);
|
||||
GameApplication obj = (GameApplication)ToLua.CheckObject<GameApplication>(L, 1);
|
||||
LuaFunction arg0 = ToLua.CheckLuaFunction(L, 2);
|
||||
obj.PickAndUploadImage(arg0);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return LuaDLL.toluaL_exception(L, e);
|
||||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
|
||||
static int op_Equality(IntPtr L)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c87c4fe551df54b4ba4a9526ac652ded
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ae3d47c06054bdb4d865c060f9310804
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1 +1 @@
|
|||
[{"bundle":"base/base_script","lua_path":"/tolua_project,/base_project,/main_project","check":true,"size":2010011,"name":"base_script","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/common","check":true,"size":16793054,"name":"common","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/login","check":true,"size":602811,"name":"login","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/lobby","check":true,"size":12116548,"name":"lobby","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/family","check":true,"size":5047323,"name":"family","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/chat","check":true,"size":602524,"name":"chat","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/newgroup","check":true,"size":1941001,"name":"newgroup","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/rank","check":true,"size":165367,"name":"rank","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_majiang","check":true,"size":11706906,"name":"main_majiang","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_poker","check":true,"size":8010045,"name":"main_poker","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_zipai","check":true,"size":9126303,"name":"main_zipai","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/static","check":true,"size":35706766,"name":"static","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/embed","check":true,"size":898919,"name":"embed","ver":"1.0.4","version":"1.0.4","is_res":true},{"bundle":"base/main_pokemajiang","check":true,"size":6192705,"name":"main_pokemajiang","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_zipaimajiang","check":true,"size":8984762,"name":"main_zipaimajiang","ver":"1.0.4","version":"1.0.4"}]
|
||||
[{"bundle":"base/base_script","lua_path":"/tolua_project,/base_project,/main_project","check":true,"size":2018202,"name":"base_script","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/common","check":true,"size":16793054,"name":"common","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/login","check":true,"size":602811,"name":"login","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/lobby","check":true,"size":12116548,"name":"lobby","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/family","check":true,"size":5073460,"name":"family","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/chat","check":true,"size":602524,"name":"chat","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/newgroup","check":true,"size":1941001,"name":"newgroup","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/rank","check":true,"size":165367,"name":"rank","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_majiang","check":true,"size":11706906,"name":"main_majiang","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_poker","check":true,"size":8010045,"name":"main_poker","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_zipai","check":true,"size":9126303,"name":"main_zipai","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/static","check":true,"size":35706766,"name":"static","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/embed","check":true,"size":898919,"name":"embed","ver":"1.0.4","version":"1.0.4","is_res":true},{"bundle":"base/main_pokemajiang","check":true,"size":6192705,"name":"main_pokemajiang","ver":"1.0.4","version":"1.0.4"},{"bundle":"base/main_zipaimajiang","check":true,"size":8984762,"name":"main_zipaimajiang","ver":"1.0.4","version":"1.0.4"}]
|
||||