From a4edb99d595ee75c3beda4473bacb76d8cae85f2 Mon Sep 17 00:00:00 2001 From: Dango2887 Date: Wed, 9 Oct 2024 19:41:50 +0800 Subject: [PATCH] feat:update upm --- Editor.meta | 8 + Editor/BuildTargetUtils.cs | 14 + Editor/BuildTargetUtils.cs.meta | 11 + Editor/LinkXMLGenerator.cs | 70 ++ Editor/LinkXMLGenerator.cs.meta | 11 + Editor/Plist.cs | 954 ++++++++++++++++++ Editor/Plist.cs.meta | 3 + Editor/SDKLinkProcessBuild.cs | 119 +++ Editor/SDKLinkProcessBuild.cs.meta | 11 + Editor/TapFileHelper.cs | 168 +++ Editor/TapFileHelper.cs.meta | 3 + Editor/TapSDK.Core.Editor.asmdef | 15 + Editor/TapSDK.Core.Editor.asmdef.meta | 7 + Editor/TapSDKCoreCompile.cs | 292 ++++++ Editor/TapSDKCoreCompile.cs.meta | 3 + Editor/TapSDKCoreIOSProcessor.cs | 74 ++ Editor/TapSDKCoreIOSProcessor.cs.meta | 3 + Editor/UI.meta | 9 + Editor/UI/ScrollViewEditor.cs | 267 +++++ Editor/UI/ScrollViewEditor.cs.meta | 12 + Editor/UI/ScrollViewExEditor.cs | 34 + Editor/UI/ScrollViewExEditor.cs.meta | 12 + Editor/UI/TapSDK.UI.Editor.asmdef | 17 + Editor/UI/TapSDK.UI.Editor.asmdef.meta | 7 + Mobile.meta | 8 + Mobile/Editor.meta | 8 + Mobile/Editor/NativeDependencies.xml | 16 + Mobile/Editor/NativeDependencies.xml.meta | 7 + Mobile/Editor/TapCommonMobileProcessBuild.cs | 20 + .../TapCommonMobileProcessBuild.cs.meta | 11 + .../Editor/TapSDK.Core.Mobile.Editor.asmdef | 17 + .../TapSDK.Core.Mobile.Editor.asmdef.meta | 7 + Mobile/Runtime.meta | 8 + Mobile/Runtime/AndroidNativeWrapper.cs | 50 + Mobile/Runtime/AndroidNativeWrapper.cs.meta | 11 + Mobile/Runtime/Bridge.cs | 84 ++ Mobile/Runtime/Bridge.cs.meta | 11 + Mobile/Runtime/BridgeAndroid.cs | 73 ++ Mobile/Runtime/BridgeAndroid.cs.meta | 11 + Mobile/Runtime/BridgeCallback.cs | 35 + Mobile/Runtime/BridgeCallback.cs.meta | 11 + Mobile/Runtime/BridgeIOS.cs | 113 +++ Mobile/Runtime/BridgeIOS.cs.meta | 11 + Mobile/Runtime/Command.cs | 119 +++ Mobile/Runtime/Command.cs.meta | 11 + Mobile/Runtime/Constants.cs | 9 + Mobile/Runtime/Constants.cs.meta | 11 + Mobile/Runtime/EngineBridgeInitializer.cs | 33 + .../Runtime/EngineBridgeInitializer.cs.meta | 11 + Mobile/Runtime/IBridge.cs | 15 + Mobile/Runtime/IBridge.cs.meta | 11 + Mobile/Runtime/IOSNativeWrapper.cs | 273 +++++ Mobile/Runtime/IOSNativeWrapper.cs.meta | 11 + Mobile/Runtime/Result.cs | 37 + Mobile/Runtime/Result.cs.meta | 11 + Mobile/Runtime/TapCoreMobile.cs | 71 ++ Mobile/Runtime/TapCoreMobile.cs.meta | 11 + Mobile/Runtime/TapEventMobile.cs | 265 +++++ Mobile/Runtime/TapEventMobile.cs.meta | 11 + .../Runtime/TapSDK.Core.Mobile.Runtime.asmdef | 18 + .../TapSDK.Core.Mobile.Runtime.asmdef.meta | 7 + Mobile/Runtime/TapUUID.cs | 9 + Mobile/Runtime/TapUUID.cs.meta | 11 + Resources.meta | 8 + Resources/Fonts.meta | 8 + Resources/Fonts/taptap-sdk-bold.ttf | Bin 0 -> 9244 bytes Resources/Fonts/taptap-sdk-bold.ttf.meta | 22 + Resources/Fonts/taptap-sdk.ttf | Bin 0 -> 9972 bytes Resources/Fonts/taptap-sdk.ttf.meta | 21 + Resources/Loading.prefab | 260 +++++ Resources/Loading.prefab.meta | 7 + Resources/TapCommonTip.prefab | 194 ++++ Resources/TapCommonTip.prefab.meta | 7 + Resources/TapCommonToastBlack.prefab | 528 ++++++++++ Resources/TapCommonToastBlack.prefab.meta | 7 + Resources/TapCommonToastWhite.prefab | 411 ++++++++ Resources/TapCommonToastWhite.prefab.meta | 7 + Resources/TapMessage.prefab | 352 +++++++ Resources/TapMessage.prefab.meta | 10 + Resources/TapSDKCommonTapIcon-v2.png | Bin 0 -> 923 bytes Resources/TapSDKCommonTapIcon-v2.png.meta | 128 +++ Resources/TapSDKCommonTapIcon.png | Bin 0 -> 2057 bytes Resources/TapSDKCommonTapIcon.png.meta | 128 +++ Resources/TapSDKCommonToastBg.png | Bin 0 -> 1355 bytes Resources/TapSDKCommonToastBg.png.meta | 128 +++ Resources/TapSDKConstantUIRoot.prefab | 100 ++ Resources/TapSDKConstantUIRoot.prefab.meta | 7 + Resources/TapSDKUIRoot.prefab | 100 ++ Resources/TapSDKUIRoot.prefab.meta | 7 + Resources/TapTapBtn_White.png | Bin 0 -> 1042 bytes Resources/TapTapBtn_White.png.meta | 128 +++ Resources/TapTapBtn_White_2.png | Bin 0 -> 159 bytes Resources/TapTapBtn_White_2.png.meta | 128 +++ Resources/ToastBackground.png | Bin 0 -> 13759 bytes Resources/ToastBackground.png.meta | 128 +++ Resources/detail_bg.png | Bin 0 -> 3114 bytes Resources/detail_bg.png.meta | 92 ++ Resources/success.png | Bin 0 -> 1576 bytes Resources/success.png.meta | 128 +++ Resources/tap_toast_background.png | Bin 0 -> 304 bytes Resources/tap_toast_background.png.meta | 128 +++ Resources/tap_toast_background1.png | Bin 0 -> 1172 bytes Resources/tap_toast_background1.png.meta | 128 +++ Resources/taptap-bg.png | Bin 0 -> 43431 bytes Resources/taptap-bg.png.meta | 140 +++ Resources/taptap-close.png | Bin 0 -> 565 bytes Resources/taptap-close.png.meta | 146 +++ Resources/taptap-router-v2.png | Bin 0 -> 1292 bytes Resources/taptap-router-v2.png.meta | 128 +++ Resources/taptap-router.png | Bin 0 -> 3915 bytes Resources/taptap-router.png.meta | 146 +++ Resources/taptap-sdk-refresh 1.png | Bin 0 -> 6252 bytes Resources/taptap-sdk-refresh 1.png.meta | 128 +++ Resources/taptap-toast-error.png | Bin 0 -> 585 bytes Resources/taptap-toast-error.png.meta | 128 +++ Resources/taptap-toast-info.png | Bin 0 -> 510 bytes Resources/taptap-toast-info.png.meta | 128 +++ Resources/taptap-toast-success.png | Bin 0 -> 533 bytes Resources/taptap-toast-success.png.meta | 128 +++ Resources/taptap-toast-warning.png | Bin 0 -> 466 bytes Resources/taptap-toast-warning.png.meta | 128 +++ Runtime.meta | 8 + Runtime/Internal.meta | 8 + Runtime/Internal/Http.meta | 8 + Runtime/Internal/Http/TapHttpClient.cs | 176 ++++ Runtime/Internal/Http/TapHttpClient.cs.meta | 11 + Runtime/Internal/Http/TapHttpUtils.cs | 83 ++ Runtime/Internal/Http/TapHttpUtils.cs.meta | 11 + Runtime/Internal/Init.meta | 8 + Runtime/Internal/Init/IInitTask.cs | 18 + Runtime/Internal/Init/IInitTask.cs.meta | 11 + Runtime/Internal/Json.meta | 8 + Runtime/Internal/Json/TapJsonConverter.cs | 40 + .../Internal/Json/TapJsonConverter.cs.meta | 11 + Runtime/Internal/Log.meta | 8 + Runtime/Internal/Log/TapLog.cs | 117 +++ Runtime/Internal/Log/TapLog.cs.meta | 11 + Runtime/Internal/Platform.meta | 8 + Runtime/Internal/Platform/ITapCorePlatform.cs | 12 + .../Platform/ITapCorePlatform.cs.meta | 11 + .../Internal/Platform/ITapEventPlatform.cs | 37 + .../Platform/ITapEventPlatform.cs.meta | 11 + .../Internal/Platform/PlatformTypeUtils.cs | 46 + .../Platform/PlatformTypeUtils.cs.meta | 11 + Runtime/Internal/UI.meta | 8 + Runtime/Internal/UI/Base.meta | 8 + Runtime/Internal/UI/Base/Const.cs | 27 + Runtime/Internal/UI/Base/Const.cs.meta | 11 + .../UI/Base/GraphicRaycasterBugFixed.cs | 240 +++++ .../UI/Base/GraphicRaycasterBugFixed.cs.meta | 3 + .../UI/Base/LoadingPanelController.cs | 29 + .../UI/Base/LoadingPanelController.cs.meta | 11 + Runtime/Internal/UI/Base/MonoSingleton.cs | 102 ++ .../Internal/UI/Base/MonoSingleton.cs.meta | 11 + Runtime/Internal/UI/Base/Singleton.cs | 29 + Runtime/Internal/UI/Base/Singleton.cs.meta | 11 + .../Internal/UI/Base/TipPanelController.cs | 45 + .../UI/Base/TipPanelController.cs.meta | 3 + .../UI/Base/ToastBlackPanelController.cs | 95 ++ .../UI/Base/ToastBlackPanelController.cs.meta | 11 + .../UI/Base/ToastWhitePanelController.cs | 79 ++ .../UI/Base/ToastWhitePanelController.cs.meta | 3 + Runtime/Internal/UI/Base/UIManager.cs | 724 +++++++++++++ Runtime/Internal/UI/Base/UIManager.cs.meta | 11 + Runtime/Internal/UI/BasePanel.meta | 8 + .../UI/BasePanel/BasePanelController.cs | 385 +++++++ .../UI/BasePanel/BasePanelController.cs.meta | 11 + Runtime/Internal/UI/Params.meta | 8 + Runtime/Internal/UI/Params/BasePanelConfig.cs | 18 + .../UI/Params/BasePanelConfig.cs.meta | 11 + .../Internal/UI/Params/IOpenPanelParameter.cs | 7 + .../UI/Params/IOpenPanelParameter.cs.meta | 11 + Runtime/Internal/UI/ScrollViewEx.meta | 8 + Runtime/Internal/UI/ScrollViewEx/ObjPool.meta | 9 + .../UI/ScrollViewEx/ObjPool/SimpleObjPool.cs | 76 ++ .../ObjPool/SimpleObjPool.cs.meta | 12 + .../Internal/UI/ScrollViewEx/ScrollView.meta | 9 + .../UI/ScrollViewEx/ScrollView/ScrollView.cs | 828 +++++++++++++++ .../ScrollView/ScrollView.cs.meta | 12 + .../ScrollViewEx/ScrollView/ScrollViewEx.cs | 245 +++++ .../ScrollView/ScrollViewEx.cs.meta | 12 + Runtime/Internal/Utils.meta | 8 + Runtime/Internal/Utils/BridgeUtils.cs | 29 + Runtime/Internal/Utils/BridgeUtils.cs.meta | 11 + Runtime/Internal/Utils/ImageUtils.cs | 147 +++ Runtime/Internal/Utils/ImageUtils.cs.meta | 11 + Runtime/Internal/Utils/TapLoom.cs | 155 +++ Runtime/Internal/Utils/TapLoom.cs.meta | 11 + Runtime/Internal/Utils/TapMessage.cs | 69 ++ Runtime/Internal/Utils/TapMessage.cs.meta | 13 + Runtime/Internal/Utils/UrlUtils.cs | 34 + Runtime/Internal/Utils/UrlUtils.cs.meta | 11 + Runtime/Public.meta | 8 + Runtime/Public/DataStorage.cs | 141 +++ Runtime/Public/DataStorage.cs.meta | 11 + Runtime/Public/ITapPropertiesProxy.cs | 6 + Runtime/Public/ITapPropertiesProxy.cs.meta | 11 + Runtime/Public/Json.cs | 636 ++++++++++++ Runtime/Public/Json.cs.meta | 11 + Runtime/Public/Log.meta | 8 + Runtime/Public/Log/TapLogLevel.cs | 7 + Runtime/Public/Log/TapLogLevel.cs.meta | 11 + Runtime/Public/Log/TapLogger.cs | 48 + Runtime/Public/Log/TapLogger.cs.meta | 11 + Runtime/Public/Platform.cs | 27 + Runtime/Public/Platform.cs.meta | 11 + Runtime/Public/RegionType.cs | 8 + Runtime/Public/RegionType.cs.meta | 11 + Runtime/Public/SafeDictionary.cs | 27 + Runtime/Public/SafeDictionary.cs.meta | 11 + Runtime/Public/TapError.cs | 62 ++ Runtime/Public/TapError.cs.meta | 11 + Runtime/Public/TapErrorCode.cs | 44 + Runtime/Public/TapErrorCode.cs.meta | 11 + Runtime/Public/TapException.cs | 24 + Runtime/Public/TapException.cs.meta | 11 + Runtime/Public/TapLanguage.cs | 21 + Runtime/Public/TapLanguage.cs.meta | 11 + Runtime/Public/TapLocalizeManager.cs | 128 +++ Runtime/Public/TapLocalizeManager.cs.meta | 11 + Runtime/Public/TapTapEvent.cs | 93 ++ Runtime/Public/TapTapEvent.cs.meta | 11 + Runtime/Public/TapTapSDK.cs | 99 ++ Runtime/Public/TapTapSDK.cs.meta | 11 + Runtime/Public/TapTapSdkOptions.cs | 104 ++ Runtime/Public/TapTapSdkOptions.cs.meta | 11 + Runtime/TapSDK.Core.Runtime.asmdef | 3 + Runtime/TapSDK.Core.Runtime.asmdef.meta | 7 + Standalone.meta | 8 + Standalone/Editor.meta | 8 + .../Editor/TapCoreStandaloneProcessBuild.cs | 20 + .../TapCoreStandaloneProcessBuild.cs.meta | 11 + .../TapSDK.Core.Standalone.Editor.asmdef | 17 + .../TapSDK.Core.Standalone.Editor.asmdef.meta | 7 + Standalone/Plugins.meta | 8 + Standalone/Plugins/macOS.meta | 8 + .../Plugins/macOS/TapDBDeviceInfo.bundle.meta | 33 + .../TapDBDeviceInfo.bundle/Contents.meta | 8 + .../Contents/Info.plist | 46 + .../Contents/Info.plist.meta | 7 + .../Contents/MacOS.meta | 8 + .../Contents/MacOS/TapDBDeviceInfo | Bin 0 -> 167584 bytes .../Contents/MacOS/TapDBDeviceInfo.meta | 7 + .../Contents/_CodeSignature.meta | 8 + .../Contents/_CodeSignature/CodeResources | 115 +++ .../_CodeSignature/CodeResources.meta | 7 + Standalone/Runtime.meta | 8 + Standalone/Runtime/Internal.meta | 8 + Standalone/Runtime/Internal/Bean.meta | 8 + .../Runtime/Internal/Bean/TapGatekeeper.cs | 42 + .../Internal/Bean/TapGatekeeper.cs.meta | 11 + Standalone/Runtime/Internal/Constants.cs | 37 + Standalone/Runtime/Internal/Constants.cs.meta | 11 + Standalone/Runtime/Internal/DeviceInfo.cs | 152 +++ .../Runtime/Internal/DeviceInfo.cs.meta | 11 + Standalone/Runtime/Internal/EventSender.cs | 250 +++++ .../Runtime/Internal/EventSender.cs.meta | 11 + Standalone/Runtime/Internal/Http.meta | 8 + Standalone/Runtime/Internal/Http/TapHttp.cs | 377 +++++++ .../Runtime/Internal/Http/TapHttp.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpBuilder.cs | 92 ++ .../Internal/Http/TapHttpBuilder.cs.meta | 11 + .../Internal/Http/TapHttpErrorConstants.cs | 114 +++ .../Http/TapHttpErrorConstants.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpException.cs | 71 ++ .../Internal/Http/TapHttpException.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpParser.cs | 132 +++ .../Internal/Http/TapHttpParser.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpResponse.cs | 37 + .../Internal/Http/TapHttpResponse.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpResult.cs | 90 ++ .../Internal/Http/TapHttpResult.cs.meta | 11 + .../Internal/Http/TapHttpRetryStrategy.cs | 237 +++++ .../Http/TapHttpRetryStrategy.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpSign.cs | 137 +++ .../Runtime/Internal/Http/TapHttpSign.cs.meta | 11 + .../Runtime/Internal/Http/TapHttpUtils.cs | 166 +++ .../Internal/Http/TapHttpUtils.cs.meta | 11 + Standalone/Runtime/Internal/Identity.cs | 43 + Standalone/Runtime/Internal/Identity.cs.meta | 11 + Standalone/Runtime/Internal/Openlog.meta | 8 + Standalone/Runtime/Internal/Openlog/Bean.meta | 8 + .../Openlog/Bean/TapOpenlogLogGroup.cs | 39 + .../Openlog/Bean/TapOpenlogLogGroup.cs.meta | 11 + .../Openlog/Bean/TapOpenlogStoreBean.cs | 33 + .../Openlog/Bean/TapOpenlogStoreBean.cs.meta | 11 + .../Internal/Openlog/TapOpenlogHttpClient.cs | 153 +++ .../Openlog/TapOpenlogHttpClient.cs.meta | 11 + .../Openlog/TapOpenlogParamConstants.cs | 104 ++ .../Openlog/TapOpenlogParamConstants.cs.meta | 11 + .../Internal/Openlog/TapOpenlogQueueBase.cs | 198 ++++ .../Openlog/TapOpenlogQueueBase.cs.meta | 11 + .../Openlog/TapOpenlogQueueBusiness.cs | 17 + .../Openlog/TapOpenlogQueueBusiness.cs.meta | 11 + .../Openlog/TapOpenlogQueueTechnology.cs | 15 + .../Openlog/TapOpenlogQueueTechnology.cs.meta | 11 + .../Internal/Openlog/TapOpenlogStandalone.cs | 190 ++++ .../Openlog/TapOpenlogStandalone.cs.meta | 11 + Standalone/Runtime/Internal/PlayRecorder.cs | 53 + .../Runtime/Internal/PlayRecorder.cs.meta | 11 + Standalone/Runtime/Internal/Prefs.cs | 85 ++ Standalone/Runtime/Internal/Prefs.cs.meta | 11 + Standalone/Runtime/Internal/Tracker.cs | 318 ++++++ Standalone/Runtime/Internal/Tracker.cs.meta | 11 + Standalone/Runtime/Internal/User.cs | 51 + Standalone/Runtime/Internal/User.cs.meta | 11 + Standalone/Runtime/Internal/Utils.meta | 8 + .../Runtime/Internal/Utils/TapProtoBufffer.cs | 28 + .../Internal/Utils/TapProtoBufffer.cs.meta | 11 + Standalone/Runtime/Internal/service.meta | 8 + .../Internal/service/ITapLoginService.cs | 9 + .../Internal/service/ITapLoginService.cs.meta | 11 + Standalone/Runtime/Public.meta | 8 + Standalone/Runtime/Public/EventManager.cs | 41 + .../Runtime/Public/EventManager.cs.meta | 11 + .../Runtime/Public/TapCoreStandalone.cs | 186 ++++ .../Runtime/Public/TapCoreStandalone.cs.meta | 11 + .../Runtime/Public/TapEventStandalone.cs | 281 ++++++ .../Runtime/Public/TapEventStandalone.cs.meta | 11 + .../TapSDK.Core.Standalone.Runtime.asmdef | 21 + ...TapSDK.Core.Standalone.Runtime.asmdef.meta | 7 + link.xml | 4 + link.xml.meta | 7 + package.json | 11 + package.json.meta | 7 + 325 files changed, 18656 insertions(+) create mode 100644 Editor.meta create mode 100644 Editor/BuildTargetUtils.cs create mode 100644 Editor/BuildTargetUtils.cs.meta create mode 100644 Editor/LinkXMLGenerator.cs create mode 100644 Editor/LinkXMLGenerator.cs.meta create mode 100644 Editor/Plist.cs create mode 100644 Editor/Plist.cs.meta create mode 100644 Editor/SDKLinkProcessBuild.cs create mode 100644 Editor/SDKLinkProcessBuild.cs.meta create mode 100644 Editor/TapFileHelper.cs create mode 100644 Editor/TapFileHelper.cs.meta create mode 100644 Editor/TapSDK.Core.Editor.asmdef create mode 100644 Editor/TapSDK.Core.Editor.asmdef.meta create mode 100644 Editor/TapSDKCoreCompile.cs create mode 100644 Editor/TapSDKCoreCompile.cs.meta create mode 100644 Editor/TapSDKCoreIOSProcessor.cs create mode 100644 Editor/TapSDKCoreIOSProcessor.cs.meta create mode 100644 Editor/UI.meta create mode 100644 Editor/UI/ScrollViewEditor.cs create mode 100644 Editor/UI/ScrollViewEditor.cs.meta create mode 100644 Editor/UI/ScrollViewExEditor.cs create mode 100644 Editor/UI/ScrollViewExEditor.cs.meta create mode 100644 Editor/UI/TapSDK.UI.Editor.asmdef create mode 100644 Editor/UI/TapSDK.UI.Editor.asmdef.meta create mode 100644 Mobile.meta create mode 100644 Mobile/Editor.meta create mode 100644 Mobile/Editor/NativeDependencies.xml create mode 100644 Mobile/Editor/NativeDependencies.xml.meta create mode 100644 Mobile/Editor/TapCommonMobileProcessBuild.cs create mode 100644 Mobile/Editor/TapCommonMobileProcessBuild.cs.meta create mode 100644 Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef create mode 100644 Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef.meta create mode 100644 Mobile/Runtime.meta create mode 100644 Mobile/Runtime/AndroidNativeWrapper.cs create mode 100644 Mobile/Runtime/AndroidNativeWrapper.cs.meta create mode 100644 Mobile/Runtime/Bridge.cs create mode 100644 Mobile/Runtime/Bridge.cs.meta create mode 100644 Mobile/Runtime/BridgeAndroid.cs create mode 100644 Mobile/Runtime/BridgeAndroid.cs.meta create mode 100644 Mobile/Runtime/BridgeCallback.cs create mode 100644 Mobile/Runtime/BridgeCallback.cs.meta create mode 100644 Mobile/Runtime/BridgeIOS.cs create mode 100644 Mobile/Runtime/BridgeIOS.cs.meta create mode 100644 Mobile/Runtime/Command.cs create mode 100644 Mobile/Runtime/Command.cs.meta create mode 100644 Mobile/Runtime/Constants.cs create mode 100644 Mobile/Runtime/Constants.cs.meta create mode 100644 Mobile/Runtime/EngineBridgeInitializer.cs create mode 100644 Mobile/Runtime/EngineBridgeInitializer.cs.meta create mode 100644 Mobile/Runtime/IBridge.cs create mode 100644 Mobile/Runtime/IBridge.cs.meta create mode 100644 Mobile/Runtime/IOSNativeWrapper.cs create mode 100644 Mobile/Runtime/IOSNativeWrapper.cs.meta create mode 100644 Mobile/Runtime/Result.cs create mode 100644 Mobile/Runtime/Result.cs.meta create mode 100644 Mobile/Runtime/TapCoreMobile.cs create mode 100644 Mobile/Runtime/TapCoreMobile.cs.meta create mode 100644 Mobile/Runtime/TapEventMobile.cs create mode 100644 Mobile/Runtime/TapEventMobile.cs.meta create mode 100644 Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef create mode 100644 Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef.meta create mode 100644 Mobile/Runtime/TapUUID.cs create mode 100644 Mobile/Runtime/TapUUID.cs.meta create mode 100644 Resources.meta create mode 100644 Resources/Fonts.meta create mode 100644 Resources/Fonts/taptap-sdk-bold.ttf create mode 100644 Resources/Fonts/taptap-sdk-bold.ttf.meta create mode 100644 Resources/Fonts/taptap-sdk.ttf create mode 100644 Resources/Fonts/taptap-sdk.ttf.meta create mode 100644 Resources/Loading.prefab create mode 100644 Resources/Loading.prefab.meta create mode 100644 Resources/TapCommonTip.prefab create mode 100644 Resources/TapCommonTip.prefab.meta create mode 100644 Resources/TapCommonToastBlack.prefab create mode 100644 Resources/TapCommonToastBlack.prefab.meta create mode 100644 Resources/TapCommonToastWhite.prefab create mode 100644 Resources/TapCommonToastWhite.prefab.meta create mode 100644 Resources/TapMessage.prefab create mode 100755 Resources/TapMessage.prefab.meta create mode 100644 Resources/TapSDKCommonTapIcon-v2.png create mode 100644 Resources/TapSDKCommonTapIcon-v2.png.meta create mode 100644 Resources/TapSDKCommonTapIcon.png create mode 100644 Resources/TapSDKCommonTapIcon.png.meta create mode 100644 Resources/TapSDKCommonToastBg.png create mode 100644 Resources/TapSDKCommonToastBg.png.meta create mode 100644 Resources/TapSDKConstantUIRoot.prefab create mode 100644 Resources/TapSDKConstantUIRoot.prefab.meta create mode 100644 Resources/TapSDKUIRoot.prefab create mode 100644 Resources/TapSDKUIRoot.prefab.meta create mode 100644 Resources/TapTapBtn_White.png create mode 100644 Resources/TapTapBtn_White.png.meta create mode 100644 Resources/TapTapBtn_White_2.png create mode 100644 Resources/TapTapBtn_White_2.png.meta create mode 100644 Resources/ToastBackground.png create mode 100644 Resources/ToastBackground.png.meta create mode 100644 Resources/detail_bg.png create mode 100644 Resources/detail_bg.png.meta create mode 100644 Resources/success.png create mode 100644 Resources/success.png.meta create mode 100755 Resources/tap_toast_background.png create mode 100644 Resources/tap_toast_background.png.meta create mode 100755 Resources/tap_toast_background1.png create mode 100644 Resources/tap_toast_background1.png.meta create mode 100644 Resources/taptap-bg.png create mode 100644 Resources/taptap-bg.png.meta create mode 100644 Resources/taptap-close.png create mode 100644 Resources/taptap-close.png.meta create mode 100644 Resources/taptap-router-v2.png create mode 100644 Resources/taptap-router-v2.png.meta create mode 100644 Resources/taptap-router.png create mode 100644 Resources/taptap-router.png.meta create mode 100644 Resources/taptap-sdk-refresh 1.png create mode 100644 Resources/taptap-sdk-refresh 1.png.meta create mode 100644 Resources/taptap-toast-error.png create mode 100644 Resources/taptap-toast-error.png.meta create mode 100644 Resources/taptap-toast-info.png create mode 100644 Resources/taptap-toast-info.png.meta create mode 100644 Resources/taptap-toast-success.png create mode 100644 Resources/taptap-toast-success.png.meta create mode 100644 Resources/taptap-toast-warning.png create mode 100644 Resources/taptap-toast-warning.png.meta create mode 100644 Runtime.meta create mode 100644 Runtime/Internal.meta create mode 100644 Runtime/Internal/Http.meta create mode 100644 Runtime/Internal/Http/TapHttpClient.cs create mode 100644 Runtime/Internal/Http/TapHttpClient.cs.meta create mode 100644 Runtime/Internal/Http/TapHttpUtils.cs create mode 100644 Runtime/Internal/Http/TapHttpUtils.cs.meta create mode 100644 Runtime/Internal/Init.meta create mode 100644 Runtime/Internal/Init/IInitTask.cs create mode 100644 Runtime/Internal/Init/IInitTask.cs.meta create mode 100644 Runtime/Internal/Json.meta create mode 100644 Runtime/Internal/Json/TapJsonConverter.cs create mode 100644 Runtime/Internal/Json/TapJsonConverter.cs.meta create mode 100644 Runtime/Internal/Log.meta create mode 100644 Runtime/Internal/Log/TapLog.cs create mode 100644 Runtime/Internal/Log/TapLog.cs.meta create mode 100644 Runtime/Internal/Platform.meta create mode 100644 Runtime/Internal/Platform/ITapCorePlatform.cs create mode 100644 Runtime/Internal/Platform/ITapCorePlatform.cs.meta create mode 100644 Runtime/Internal/Platform/ITapEventPlatform.cs create mode 100644 Runtime/Internal/Platform/ITapEventPlatform.cs.meta create mode 100644 Runtime/Internal/Platform/PlatformTypeUtils.cs create mode 100644 Runtime/Internal/Platform/PlatformTypeUtils.cs.meta create mode 100644 Runtime/Internal/UI.meta create mode 100644 Runtime/Internal/UI/Base.meta create mode 100644 Runtime/Internal/UI/Base/Const.cs create mode 100644 Runtime/Internal/UI/Base/Const.cs.meta create mode 100644 Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs create mode 100644 Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs.meta create mode 100644 Runtime/Internal/UI/Base/LoadingPanelController.cs create mode 100644 Runtime/Internal/UI/Base/LoadingPanelController.cs.meta create mode 100644 Runtime/Internal/UI/Base/MonoSingleton.cs create mode 100644 Runtime/Internal/UI/Base/MonoSingleton.cs.meta create mode 100644 Runtime/Internal/UI/Base/Singleton.cs create mode 100644 Runtime/Internal/UI/Base/Singleton.cs.meta create mode 100644 Runtime/Internal/UI/Base/TipPanelController.cs create mode 100644 Runtime/Internal/UI/Base/TipPanelController.cs.meta create mode 100644 Runtime/Internal/UI/Base/ToastBlackPanelController.cs create mode 100644 Runtime/Internal/UI/Base/ToastBlackPanelController.cs.meta create mode 100644 Runtime/Internal/UI/Base/ToastWhitePanelController.cs create mode 100644 Runtime/Internal/UI/Base/ToastWhitePanelController.cs.meta create mode 100644 Runtime/Internal/UI/Base/UIManager.cs create mode 100644 Runtime/Internal/UI/Base/UIManager.cs.meta create mode 100644 Runtime/Internal/UI/BasePanel.meta create mode 100644 Runtime/Internal/UI/BasePanel/BasePanelController.cs create mode 100644 Runtime/Internal/UI/BasePanel/BasePanelController.cs.meta create mode 100644 Runtime/Internal/UI/Params.meta create mode 100644 Runtime/Internal/UI/Params/BasePanelConfig.cs create mode 100644 Runtime/Internal/UI/Params/BasePanelConfig.cs.meta create mode 100644 Runtime/Internal/UI/Params/IOpenPanelParameter.cs create mode 100644 Runtime/Internal/UI/Params/IOpenPanelParameter.cs.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx/ObjPool.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs create mode 100644 Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx/ScrollView.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs create mode 100644 Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs.meta create mode 100644 Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs create mode 100644 Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs.meta create mode 100644 Runtime/Internal/Utils.meta create mode 100644 Runtime/Internal/Utils/BridgeUtils.cs create mode 100644 Runtime/Internal/Utils/BridgeUtils.cs.meta create mode 100644 Runtime/Internal/Utils/ImageUtils.cs create mode 100644 Runtime/Internal/Utils/ImageUtils.cs.meta create mode 100644 Runtime/Internal/Utils/TapLoom.cs create mode 100644 Runtime/Internal/Utils/TapLoom.cs.meta create mode 100755 Runtime/Internal/Utils/TapMessage.cs create mode 100755 Runtime/Internal/Utils/TapMessage.cs.meta create mode 100644 Runtime/Internal/Utils/UrlUtils.cs create mode 100644 Runtime/Internal/Utils/UrlUtils.cs.meta create mode 100644 Runtime/Public.meta create mode 100644 Runtime/Public/DataStorage.cs create mode 100644 Runtime/Public/DataStorage.cs.meta create mode 100644 Runtime/Public/ITapPropertiesProxy.cs create mode 100644 Runtime/Public/ITapPropertiesProxy.cs.meta create mode 100644 Runtime/Public/Json.cs create mode 100644 Runtime/Public/Json.cs.meta create mode 100644 Runtime/Public/Log.meta create mode 100644 Runtime/Public/Log/TapLogLevel.cs create mode 100644 Runtime/Public/Log/TapLogLevel.cs.meta create mode 100644 Runtime/Public/Log/TapLogger.cs create mode 100644 Runtime/Public/Log/TapLogger.cs.meta create mode 100644 Runtime/Public/Platform.cs create mode 100644 Runtime/Public/Platform.cs.meta create mode 100644 Runtime/Public/RegionType.cs create mode 100644 Runtime/Public/RegionType.cs.meta create mode 100644 Runtime/Public/SafeDictionary.cs create mode 100644 Runtime/Public/SafeDictionary.cs.meta create mode 100644 Runtime/Public/TapError.cs create mode 100644 Runtime/Public/TapError.cs.meta create mode 100644 Runtime/Public/TapErrorCode.cs create mode 100644 Runtime/Public/TapErrorCode.cs.meta create mode 100644 Runtime/Public/TapException.cs create mode 100644 Runtime/Public/TapException.cs.meta create mode 100644 Runtime/Public/TapLanguage.cs create mode 100644 Runtime/Public/TapLanguage.cs.meta create mode 100644 Runtime/Public/TapLocalizeManager.cs create mode 100644 Runtime/Public/TapLocalizeManager.cs.meta create mode 100644 Runtime/Public/TapTapEvent.cs create mode 100644 Runtime/Public/TapTapEvent.cs.meta create mode 100644 Runtime/Public/TapTapSDK.cs create mode 100644 Runtime/Public/TapTapSDK.cs.meta create mode 100644 Runtime/Public/TapTapSdkOptions.cs create mode 100644 Runtime/Public/TapTapSdkOptions.cs.meta create mode 100644 Runtime/TapSDK.Core.Runtime.asmdef create mode 100644 Runtime/TapSDK.Core.Runtime.asmdef.meta create mode 100644 Standalone.meta create mode 100644 Standalone/Editor.meta create mode 100644 Standalone/Editor/TapCoreStandaloneProcessBuild.cs create mode 100644 Standalone/Editor/TapCoreStandaloneProcessBuild.cs.meta create mode 100644 Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef create mode 100644 Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef.meta create mode 100644 Standalone/Plugins.meta create mode 100644 Standalone/Plugins/macOS.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS.meta create mode 100755 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS/TapDBDeviceInfo create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS/TapDBDeviceInfo.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature.meta create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature/CodeResources create mode 100644 Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature/CodeResources.meta create mode 100644 Standalone/Runtime.meta create mode 100644 Standalone/Runtime/Internal.meta create mode 100644 Standalone/Runtime/Internal/Bean.meta create mode 100644 Standalone/Runtime/Internal/Bean/TapGatekeeper.cs create mode 100644 Standalone/Runtime/Internal/Bean/TapGatekeeper.cs.meta create mode 100644 Standalone/Runtime/Internal/Constants.cs create mode 100644 Standalone/Runtime/Internal/Constants.cs.meta create mode 100644 Standalone/Runtime/Internal/DeviceInfo.cs create mode 100644 Standalone/Runtime/Internal/DeviceInfo.cs.meta create mode 100644 Standalone/Runtime/Internal/EventSender.cs create mode 100644 Standalone/Runtime/Internal/EventSender.cs.meta create mode 100644 Standalone/Runtime/Internal/Http.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttp.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttp.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpBuilder.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpBuilder.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpException.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpException.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpParser.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpParser.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpResponse.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpResponse.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpResult.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpResult.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpSign.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpSign.cs.meta create mode 100644 Standalone/Runtime/Internal/Http/TapHttpUtils.cs create mode 100644 Standalone/Runtime/Internal/Http/TapHttpUtils.cs.meta create mode 100644 Standalone/Runtime/Internal/Identity.cs create mode 100644 Standalone/Runtime/Internal/Identity.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog.meta create mode 100644 Standalone/Runtime/Internal/Openlog/Bean.meta create mode 100644 Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs create mode 100644 Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs create mode 100644 Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs.meta create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs create mode 100644 Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs.meta create mode 100644 Standalone/Runtime/Internal/PlayRecorder.cs create mode 100644 Standalone/Runtime/Internal/PlayRecorder.cs.meta create mode 100644 Standalone/Runtime/Internal/Prefs.cs create mode 100644 Standalone/Runtime/Internal/Prefs.cs.meta create mode 100644 Standalone/Runtime/Internal/Tracker.cs create mode 100644 Standalone/Runtime/Internal/Tracker.cs.meta create mode 100644 Standalone/Runtime/Internal/User.cs create mode 100644 Standalone/Runtime/Internal/User.cs.meta create mode 100644 Standalone/Runtime/Internal/Utils.meta create mode 100644 Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs create mode 100644 Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs.meta create mode 100644 Standalone/Runtime/Internal/service.meta create mode 100644 Standalone/Runtime/Internal/service/ITapLoginService.cs create mode 100644 Standalone/Runtime/Internal/service/ITapLoginService.cs.meta create mode 100644 Standalone/Runtime/Public.meta create mode 100644 Standalone/Runtime/Public/EventManager.cs create mode 100644 Standalone/Runtime/Public/EventManager.cs.meta create mode 100644 Standalone/Runtime/Public/TapCoreStandalone.cs create mode 100644 Standalone/Runtime/Public/TapCoreStandalone.cs.meta create mode 100644 Standalone/Runtime/Public/TapEventStandalone.cs create mode 100644 Standalone/Runtime/Public/TapEventStandalone.cs.meta create mode 100644 Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef create mode 100644 Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef.meta create mode 100644 link.xml create mode 100644 link.xml.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..1931371 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 829b7cb0b66da498b9d1cc859956fc7c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/BuildTargetUtils.cs b/Editor/BuildTargetUtils.cs new file mode 100644 index 0000000..d0527d3 --- /dev/null +++ b/Editor/BuildTargetUtils.cs @@ -0,0 +1,14 @@ +using UnityEditor; + +namespace TapSDK.Core.Editor { + public static class BuildTargetUtils { + public static bool IsSupportMobile(BuildTarget platform) { + return platform == BuildTarget.iOS || platform == BuildTarget.Android; + } + + public static bool IsSupportStandalone(BuildTarget platform) { + return platform == BuildTarget.StandaloneOSX || + platform == BuildTarget.StandaloneWindows || platform == BuildTarget.StandaloneWindows64; + } +} +} diff --git a/Editor/BuildTargetUtils.cs.meta b/Editor/BuildTargetUtils.cs.meta new file mode 100644 index 0000000..4322832 --- /dev/null +++ b/Editor/BuildTargetUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9416fb163ae0c4cbb8d52caa63978b2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LinkXMLGenerator.cs b/Editor/LinkXMLGenerator.cs new file mode 100644 index 0000000..1aea415 --- /dev/null +++ b/Editor/LinkXMLGenerator.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.IO; +using System.Xml; +using UnityEngine; +using UnityEditor; + +namespace TapSDK.Core.Editor { + public class LinkedAssembly { + public string Fullname { get; set; } + public string[] Types { get; set; } + } + + public class LinkXMLGenerator { + public static void Generate(string path, IEnumerable assemblies) { + DirectoryInfo parent = Directory.GetParent(path); + if (!parent.Exists) { + Directory.CreateDirectory(parent.FullName); + } + + XmlDocument doc = new XmlDocument(); + + XmlNode rootNode = doc.CreateElement("linker"); + doc.AppendChild(rootNode); + + foreach (LinkedAssembly assembly in assemblies) { + XmlNode assemblyNode = doc.CreateElement("assembly"); + + XmlAttribute fullnameAttr = doc.CreateAttribute("fullname"); + fullnameAttr.Value = assembly.Fullname; + assemblyNode.Attributes.Append(fullnameAttr); + + if (assembly.Types?.Length > 0) { + foreach (string type in assembly.Types) { + XmlNode typeNode = doc.CreateElement("type"); + XmlAttribute typeFullnameAttr = doc.CreateAttribute("fullname"); + typeFullnameAttr.Value = type; + typeNode.Attributes.Append(typeFullnameAttr); + + XmlAttribute typePreserveAttr = doc.CreateAttribute("preserve"); + typePreserveAttr.Value = "all"; + typeNode.Attributes.Append(typePreserveAttr); + + assemblyNode.AppendChild(typeNode); + } + } else { + XmlAttribute preserveAttr = doc.CreateAttribute("preserve"); + preserveAttr.Value = "all"; + assemblyNode.Attributes.Append(preserveAttr); + } + + rootNode.AppendChild(assemblyNode); + } + + doc.Save(path); + AssetDatabase.Refresh(); + + Debug.Log($"Generate {path} done."); + Debug.Log(doc.OuterXml); + } + + public static void Delete(string path) { + if (File.Exists(path)) { + File.Delete(path); + AssetDatabase.Refresh(); + } + + Debug.Log($"Delete {path} done."); + } + } +} diff --git a/Editor/LinkXMLGenerator.cs.meta b/Editor/LinkXMLGenerator.cs.meta new file mode 100644 index 0000000..986f682 --- /dev/null +++ b/Editor/LinkXMLGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ded83e14866c44fe0912befabd966846 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plist.cs b/Editor/Plist.cs new file mode 100644 index 0000000..59c1aa6 --- /dev/null +++ b/Editor/Plist.cs @@ -0,0 +1,954 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; + +namespace TapSDK.Core.Editor +{ + public static class Plist + { + private static List offsetTable = new List(); + private static List objectTable = new List(); + private static int refCount; + private static int objRefSize; + private static int offsetByteSize; + private static long offsetTableOffset; + + #region Public Functions + + public static object readPlist(string path) + { + using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + return readPlist(f, plistType.Auto); + } + } + + public static object readPlistSource(string source) + { + return readPlist(System.Text.Encoding.UTF8.GetBytes(source)); + } + + public static object readPlist(byte[] data) + { + return readPlist(new MemoryStream(data), plistType.Auto); + } + + public static plistType getPlistType(Stream stream) + { + byte[] magicHeader = new byte[8]; + stream.Read(magicHeader, 0, 8); + + if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810) + { + return plistType.Binary; + } + else + { + return plistType.Xml; + } + } + + public static object readPlist(Stream stream, plistType type) + { + if (type == plistType.Auto) + { + type = getPlistType(stream); + stream.Seek(0, SeekOrigin.Begin); + } + + if (type == plistType.Binary) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + byte[] data = reader.ReadBytes((int) reader.BaseStream.Length); + return readBinary(data); + } + } + else + { + XmlDocument xml = new XmlDocument(); + xml.XmlResolver = null; + xml.Load(stream); + return readXml(xml); + } + } + + public static void writeXml(object value, string path) + { + using (StreamWriter writer = new StreamWriter(path)) + { + writer.Write(writeXml(value)); + } + } + + public static void writeXml(object value, Stream stream) + { + using (StreamWriter writer = new StreamWriter(stream)) + { + writer.Write(writeXml(value)); + } + } + + public static string writeXml(object value) + { + using (MemoryStream ms = new MemoryStream()) + { + XmlWriterSettings xmlWriterSettings = new XmlWriterSettings(); + xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false); + xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document; + xmlWriterSettings.Indent = true; + + using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings)) + { + xmlWriter.WriteStartDocument(); + //xmlWriter.WriteComment("DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); + xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", + "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null); + xmlWriter.WriteStartElement("plist"); + xmlWriter.WriteAttributeString("version", "1.0"); + compose(value, xmlWriter); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + xmlWriter.Close(); + return System.Text.Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + public static void writeBinary(object value, string path) + { + using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create))) + { + writer.Write(writeBinary(value)); + } + } + + public static void writeBinary(object value, Stream stream) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(writeBinary(value)); + } + } + + public static byte[] writeBinary(object value) + { + offsetTable.Clear(); + objectTable.Clear(); + refCount = 0; + objRefSize = 0; + offsetByteSize = 0; + offsetTableOffset = 0; + + //Do not count the root node, subtract by 1 + int totalRefs = countObject(value) - 1; + + refCount = totalRefs; + + objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length; + + composeBinary(value); + + writeBinaryString("bplist00", false); + + offsetTableOffset = (long) objectTable.Count; + + offsetTable.Add(objectTable.Count - 8); + + offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count - 1])).Length; + + List offsetBytes = new List(); + + offsetTable.Reverse(); + + for (int i = 0; i < offsetTable.Count; i++) + { + offsetTable[i] = objectTable.Count - offsetTable[i]; + byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize); + Array.Reverse(buffer); + offsetBytes.AddRange(buffer); + } + + objectTable.AddRange(offsetBytes); + + objectTable.AddRange(new byte[6]); + objectTable.Add(Convert.ToByte(offsetByteSize)); + objectTable.Add(Convert.ToByte(objRefSize)); + + var a = BitConverter.GetBytes((long) totalRefs + 1); + Array.Reverse(a); + objectTable.AddRange(a); + + objectTable.AddRange(BitConverter.GetBytes((long) 0)); + a = BitConverter.GetBytes(offsetTableOffset); + Array.Reverse(a); + objectTable.AddRange(a); + + return objectTable.ToArray(); + } + + #endregion + + #region Private Functions + + private static object readXml(XmlDocument xml) + { + XmlNode rootNode = xml.DocumentElement.ChildNodes[0]; + return parse(rootNode); + } + + private static object readBinary(byte[] data) + { + offsetTable.Clear(); + List offsetTableBytes = new List(); + objectTable.Clear(); + refCount = 0; + objRefSize = 0; + offsetByteSize = 0; + offsetTableOffset = 0; + + List bList = new List(data); + + List trailer = bList.GetRange(bList.Count - 32, 32); + + parseTrailer(trailer); + + objectTable = bList.GetRange(0, (int) offsetTableOffset); + + offsetTableBytes = bList.GetRange((int) offsetTableOffset, bList.Count - (int) offsetTableOffset - 32); + + parseOffsetTable(offsetTableBytes); + + return parseBinary(0); + } + + private static Dictionary parseDictionary(XmlNode node) + { + XmlNodeList children = node.ChildNodes; + if (children.Count % 2 != 0) + { + throw new DataMisalignedException("Dictionary elements must have an even number of child nodes"); + } + + Dictionary dict = new Dictionary(); + + for (int i = 0; i < children.Count; i += 2) + { + XmlNode keynode = children[i]; + XmlNode valnode = children[i + 1]; + + if (keynode.Name != "key") + { + throw new ApplicationException("expected a key node"); + } + + object result = parse(valnode); + + if (result != null) + { + dict.Add(keynode.InnerText, result); + } + } + + return dict; + } + + private static List parseArray(XmlNode node) + { + List array = new List(); + + foreach (XmlNode child in node.ChildNodes) + { + object result = parse(child); + if (result != null) + { + array.Add(result); + } + } + + return array; + } + + private static void composeArray(List value, XmlWriter writer) + { + writer.WriteStartElement("array"); + foreach (object obj in value) + { + compose(obj, writer); + } + + writer.WriteEndElement(); + } + + private static object parse(XmlNode node) + { + switch (node.Name) + { + case "dict": + return parseDictionary(node); + case "array": + return parseArray(node); + case "string": + return node.InnerText; + case "integer": + // int result; + //int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result); + return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo); + case "real": + return Convert.ToDouble(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo); + case "false": + return false; + case "true": + return true; + case "null": + return null; + case "date": + return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc); + case "data": + return Convert.FromBase64String(node.InnerText); + } + + throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name)); + } + + private static void compose(object value, XmlWriter writer) + { + if (value == null || value is string) + { + writer.WriteElementString("string", value as string); + } + else if (value is int || value is long) + { + writer.WriteElementString("integer", + ((int) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); + } + else if (value is System.Collections.Generic.Dictionary || + value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String")) + { + //Convert to Dictionary + Dictionary dic = value as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + IDictionary idic = (IDictionary) value; + foreach (var key in idic.Keys) + { + dic.Add(key.ToString(), idic[key]); + } + } + + writeDictionaryValues(dic, writer); + } + else if (value is List) + { + composeArray((List) value, writer); + } + else if (value is byte[]) + { + writer.WriteElementString("data", Convert.ToBase64String((Byte[]) value)); + } + else if (value is float || value is double) + { + writer.WriteElementString("real", + ((double) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); + } + else if (value is DateTime) + { + DateTime time = (DateTime) value; + string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc); + writer.WriteElementString("date", theString); //, "yyyy-MM-ddTHH:mm:ssZ")); + } + else if (value is bool) + { + writer.WriteElementString(value.ToString().ToLower(), ""); + } + else + { + throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString())); + } + } + + private static void writeDictionaryValues(Dictionary dictionary, XmlWriter writer) + { + writer.WriteStartElement("dict"); + foreach (string key in dictionary.Keys) + { + object value = dictionary[key]; + writer.WriteElementString("key", key); + compose(value, writer); + } + + writer.WriteEndElement(); + } + + private static int countObject(object value) + { + int count = 0; + switch (value.GetType().ToString()) + { + case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": + Dictionary dict = (Dictionary) value; + foreach (string key in dict.Keys) + { + count += countObject(dict[key]); + } + + count += dict.Keys.Count; + count++; + break; + case "System.Collections.Generic.List`1[System.Object]": + List list = (List) value; + foreach (object obj in list) + { + count += countObject(obj); + } + + count++; + break; + default: + count++; + break; + } + + return count; + } + + private static byte[] writeBinaryDictionary(Dictionary dictionary) + { + List buffer = new List(); + List header = new List(); + List refs = new List(); + for (int i = dictionary.Count - 1; i >= 0; i--) + { + var o = new object[dictionary.Count]; + dictionary.Values.CopyTo(o, 0); + composeBinary(o[i]); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + + for (int i = dictionary.Count - 1; i >= 0; i--) + { + var o = new string[dictionary.Count]; + dictionary.Keys.CopyTo(o, 0); + composeBinary(o[i]); //); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + + if (dictionary.Count < 15) + { + header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count))); + } + else + { + header.Add(0xD0 | 0xf); + header.AddRange(writeBinaryInteger(dictionary.Count, false)); + } + + + foreach (int val in refs) + { + byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); + Array.Reverse(refBuffer); + buffer.InsertRange(0, refBuffer); + } + + buffer.InsertRange(0, header); + + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] composeBinaryArray(List objects) + { + List buffer = new List(); + List header = new List(); + List refs = new List(); + + for (int i = objects.Count - 1; i >= 0; i--) + { + composeBinary(objects[i]); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + + if (objects.Count < 15) + { + header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count))); + } + else + { + header.Add(0xA0 | 0xf); + header.AddRange(writeBinaryInteger(objects.Count, false)); + } + + foreach (int val in refs) + { + byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); + Array.Reverse(refBuffer); + buffer.InsertRange(0, refBuffer); + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] composeBinary(object obj) + { + byte[] value; + switch (obj.GetType().ToString()) + { + case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": + value = writeBinaryDictionary((Dictionary) obj); + return value; + + case "System.Collections.Generic.List`1[System.Object]": + value = composeBinaryArray((List) obj); + return value; + + case "System.Byte[]": + value = writeBinaryByteArray((byte[]) obj); + return value; + + case "System.Double": + value = writeBinaryDouble((double) obj); + return value; + + case "System.Int32": + value = writeBinaryInteger((int) obj, true); + return value; + + case "System.String": + value = writeBinaryString((string) obj, true); + return value; + + case "System.DateTime": + value = writeBinaryDate((DateTime) obj); + return value; + + case "System.Boolean": + value = writeBinaryBool((bool) obj); + return value; + + default: + return new byte[0]; + } + } + + public static byte[] writeBinaryDate(DateTime obj) + { + List buffer = + new List(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), + 8)); + buffer.Reverse(); + buffer.Insert(0, 0x33); + objectTable.InsertRange(0, buffer); + return buffer.ToArray(); + } + + public static byte[] writeBinaryBool(bool obj) + { + List buffer = new List(new byte[1] {(bool) obj ? (byte) 9 : (byte) 8}); + objectTable.InsertRange(0, buffer); + return buffer.ToArray(); + } + + private static byte[] writeBinaryInteger(int value, bool write) + { + List buffer = new List(BitConverter.GetBytes((long) value)); + buffer = new List(RegulateNullBytes(buffer.ToArray())); + while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) + buffer.Add(0); + int header = 0x10 | (int) (Math.Log(buffer.Count) / Math.Log(2)); + + buffer.Reverse(); + + buffer.Insert(0, Convert.ToByte(header)); + + if (write) + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryDouble(double value) + { + List buffer = new List(RegulateNullBytes(BitConverter.GetBytes(value), 4)); + while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) + buffer.Add(0); + int header = 0x20 | (int) (Math.Log(buffer.Count) / Math.Log(2)); + + buffer.Reverse(); + + buffer.Insert(0, Convert.ToByte(header)); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryByteArray(byte[] value) + { + List buffer = new List(value); + List header = new List(); + if (value.Length < 15) + { + header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length))); + } + else + { + header.Add(0x40 | 0xf); + header.AddRange(writeBinaryInteger(buffer.Count, false)); + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryString(string value, bool head) + { + List buffer = new List(); + List header = new List(); + foreach (char chr in value.ToCharArray()) + buffer.Add(Convert.ToByte(chr)); + + if (head) + { + if (value.Length < 15) + { + header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length))); + } + else + { + header.Add(0x50 | 0xf); + header.AddRange(writeBinaryInteger(buffer.Count, false)); + } + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] RegulateNullBytes(byte[] value) + { + return RegulateNullBytes(value, 1); + } + + private static byte[] RegulateNullBytes(byte[] value, int minBytes) + { + Array.Reverse(value); + List bytes = new List(value); + for (int i = 0; i < bytes.Count; i++) + { + if (bytes[i] == 0 && bytes.Count > minBytes) + { + bytes.Remove(bytes[i]); + i--; + } + else + break; + } + + if (bytes.Count < minBytes) + { + int dist = minBytes - bytes.Count; + for (int i = 0; i < dist; i++) + bytes.Insert(0, 0); + } + + value = bytes.ToArray(); + Array.Reverse(value); + return value; + } + + private static void parseTrailer(List trailer) + { + offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0); + objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0); + byte[] refCountBytes = trailer.GetRange(12, 4).ToArray(); + Array.Reverse(refCountBytes); + refCount = BitConverter.ToInt32(refCountBytes, 0); + byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray(); + Array.Reverse(offsetTableOffsetBytes); + offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0); + } + + private static void parseOffsetTable(List offsetTableBytes) + { + for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize) + { + byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray(); + Array.Reverse(buffer); + offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0)); + } + } + + private static object parseBinaryDictionary(int objRef) + { + Dictionary buffer = new Dictionary(); + List refs = new List(); + int refCount = 0; + + int refStartPosition; + refCount = getCount(offsetTable[objRef], out refStartPosition); + + + if (refCount < 15) + refStartPosition = offsetTable[objRef] + 1; + else + refStartPosition = offsetTable[objRef] + 2 + + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; + + for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize) + { + byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); + Array.Reverse(refBuffer); + refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); + } + + for (int i = 0; i < refCount; i++) + { + buffer.Add((string) parseBinary(refs[i]), parseBinary(refs[i + refCount])); + } + + return buffer; + } + + private static object parseBinaryArray(int objRef) + { + List buffer = new List(); + List refs = new List(); + int refCount = 0; + + int refStartPosition; + refCount = getCount(offsetTable[objRef], out refStartPosition); + + + if (refCount < 15) + refStartPosition = offsetTable[objRef] + 1; + else + //The following integer has a header aswell so we increase the refStartPosition by two to account for that. + refStartPosition = offsetTable[objRef] + 2 + + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; + + for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize) + { + byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); + Array.Reverse(refBuffer); + refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); + } + + for (int i = 0; i < refCount; i++) + { + buffer.Add(parseBinary(refs[i])); + } + + return buffer; + } + + private static int getCount(int bytePosition, out int newBytePosition) + { + byte headerByte = objectTable[bytePosition]; + byte headerByteTrail = Convert.ToByte(headerByte & 0xf); + int count; + if (headerByteTrail < 15) + { + count = headerByteTrail; + newBytePosition = bytePosition + 1; + } + else + count = (int) parseBinaryInt(bytePosition + 1, out newBytePosition); + + return count; + } + + private static object parseBinary(int objRef) + { + byte header = objectTable[offsetTable[objRef]]; + switch (header & 0xF0) + { + case 0: + { + //If the byte is + //0 return null + //9 return true + //8 return false + return (objectTable[offsetTable[objRef]] == 0) + ? (object) null + : ((objectTable[offsetTable[objRef]] == 9) ? true : false); + } + case 0x10: + { + return parseBinaryInt(offsetTable[objRef]); + } + case 0x20: + { + return parseBinaryReal(offsetTable[objRef]); + } + case 0x30: + { + return parseBinaryDate(offsetTable[objRef]); + } + case 0x40: + { + return parseBinaryByteArray(offsetTable[objRef]); + } + case 0x50: //String ASCII + { + return parseBinaryAsciiString(offsetTable[objRef]); + } + case 0x60: //String Unicode + { + return parseBinaryUnicodeString(offsetTable[objRef]); + } + case 0xD0: + { + return parseBinaryDictionary(objRef); + } + case 0xA0: + { + return parseBinaryArray(objRef); + } + } + + throw new Exception("This type is not supported"); + } + + public static object parseBinaryDate(int headerPosition) + { + byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray(); + Array.Reverse(buffer); + double appleTime = BitConverter.ToDouble(buffer, 0); + DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime); + return result; + } + + private static object parseBinaryInt(int headerPosition) + { + int output; + return parseBinaryInt(headerPosition, out output); + } + + private static object parseBinaryInt(int headerPosition, out int newHeaderPosition) + { + byte header = objectTable[headerPosition]; + int byteCount = (int) Math.Pow(2, header & 0xf); + byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); + Array.Reverse(buffer); + //Add one to account for the header byte + newHeaderPosition = headerPosition + byteCount + 1; + return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0); + } + + private static object parseBinaryReal(int headerPosition) + { + byte header = objectTable[headerPosition]; + int byteCount = (int) Math.Pow(2, header & 0xf); + byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); + Array.Reverse(buffer); + + return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0); + } + + private static object parseBinaryAsciiString(int headerPosition) + { + int charStartPosition; + int charCount = getCount(headerPosition, out charStartPosition); + + var buffer = objectTable.GetRange(charStartPosition, charCount); + return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty; + } + + private static object parseBinaryUnicodeString(int headerPosition) + { + int charStartPosition; + int charCount = getCount(headerPosition, out charStartPosition); + charCount = charCount * 2; + + byte[] buffer = new byte[charCount]; + byte one, two; + + for (int i = 0; i < charCount; i += 2) + { + one = objectTable.GetRange(charStartPosition + i, 1)[0]; + two = objectTable.GetRange(charStartPosition + i + 1, 1)[0]; + + if (BitConverter.IsLittleEndian) + { + buffer[i] = two; + buffer[i + 1] = one; + } + else + { + buffer[i] = one; + buffer[i + 1] = two; + } + } + + return Encoding.Unicode.GetString(buffer); + } + + private static object parseBinaryByteArray(int headerPosition) + { + int byteStartPosition; + int byteCount = getCount(headerPosition, out byteStartPosition); + return objectTable.GetRange(byteStartPosition, byteCount).ToArray(); + } + + #endregion + } + + public enum plistType + { + Auto, + Binary, + Xml + } + + public static class PlistDateConverter + { + public static long timeDifference = 978307200; + + public static long GetAppleTime(long unixTime) + { + return unixTime - timeDifference; + } + + public static long GetUnixTime(long appleTime) + { + return appleTime + timeDifference; + } + + public static DateTime ConvertFromAppleTimeStamp(double timestamp) + { + DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0); + return origin.AddSeconds(timestamp); + } + + public static double ConvertToAppleTimeStamp(DateTime date) + { + DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0); + TimeSpan diff = date - begin; + return Math.Floor(diff.TotalSeconds); + } + } +} \ No newline at end of file diff --git a/Editor/Plist.cs.meta b/Editor/Plist.cs.meta new file mode 100644 index 0000000..aa164de --- /dev/null +++ b/Editor/Plist.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 96acb9ea8a9264ea6a175b32e605bfab +timeCreated: 1617120740 \ No newline at end of file diff --git a/Editor/SDKLinkProcessBuild.cs b/Editor/SDKLinkProcessBuild.cs new file mode 100644 index 0000000..24d64f7 --- /dev/null +++ b/Editor/SDKLinkProcessBuild.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using UnityEngine; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +namespace TapSDK.Core.Editor { + /// + /// 模块 SDK 生成 link.xml 构建过程 + /// + public abstract class SDKLinkProcessBuild : IPreprocessBuildWithReport, IPostprocessBuildWithReport { + /// + /// 执行顺序 + /// + public abstract int callbackOrder { get; } + + /// + /// 生成 link.xml 路径 + /// + public abstract string LinkPath { get; } + + /// + /// 防止被裁剪的 Assembly + /// + public abstract LinkedAssembly[] LinkedAssemblies { get; } + + /// + /// 执行平台委托 + /// + public abstract Func IsTargetPlatform { get; } + + /// + /// 构建时忽略目录,目前主要是 PC 内置浏览器 Vuplex + /// + public virtual string[] BuildingIgnorePaths => null; + + /// + /// 构建前处理 + /// + /// + public void OnPreprocessBuild(BuildReport report) { + if (!IsTargetPlatform.Invoke(report)) { + return; + } + + Application.logMessageReceived += OnBuildError; + IgnorePaths(); + + string linkPath = Path.Combine(Application.dataPath, LinkPath); + LinkXMLGenerator.Generate(linkPath, LinkedAssemblies); + } + + /// + /// 构建后处理 + /// + /// + public void OnPostprocessBuild(BuildReport report) { + if (!IsTargetPlatform.Invoke(report)) { + return; + } + + Application.logMessageReceived -= OnBuildError; + RecoverIgnoredPaths(); + + string linkPath = Path.Combine(Application.dataPath, LinkPath); + LinkXMLGenerator.Delete(linkPath); + } + + /// + /// 错误日志回调 + /// + /// + /// + /// + private void OnBuildError(string condition, string stacktrace, LogType logType) { + // TRICK: 通过捕获错误日志来监听打包错误事件 + if (logType == LogType.Error) { + Application.logMessageReceived -= OnBuildError; + RecoverIgnoredPaths(); + } + } + + /// + /// 忽略目录 + /// + private void IgnorePaths() { + if (BuildingIgnorePaths == null) { + return; + } + + foreach (string ignorePath in BuildingIgnorePaths) { + if (!Directory.Exists(Path.Combine(Application.dataPath, ignorePath))) { + continue; + } + string ignoreName = Path.GetFileName(ignorePath); + AssetDatabase.RenameAsset(Path.Combine("Assets", ignorePath), $"{ignoreName}~"); + } + } + + /// + /// 恢复目录 + /// + private void RecoverIgnoredPaths() { + if (BuildingIgnorePaths == null) { + return; + } + + foreach (string ignorePath in BuildingIgnorePaths) { + if (!Directory.Exists(Path.Combine(Application.dataPath, $"{ignorePath}~"))) { + continue; + } + Directory.Move(Path.Combine(Application.dataPath, $"{ignorePath}~"), + Path.Combine(Application.dataPath, $"{ignorePath}")); + AssetDatabase.ImportAsset(Path.Combine("Assets", ignorePath)); + } + } + } +} diff --git a/Editor/SDKLinkProcessBuild.cs.meta b/Editor/SDKLinkProcessBuild.cs.meta new file mode 100644 index 0000000..b280b60 --- /dev/null +++ b/Editor/SDKLinkProcessBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c724204483e1a47f8a4fe400a1353a56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/TapFileHelper.cs b/Editor/TapFileHelper.cs new file mode 100644 index 0000000..7737ba4 --- /dev/null +++ b/Editor/TapFileHelper.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace TapSDK.Core.Editor +{ + public class TapFileHelper : System.IDisposable + { + private string filePath; + + public TapFileHelper(string fPath) + { + filePath = fPath; + if (!System.IO.File.Exists(filePath)) + { + Debug.LogError(filePath + "路径下文件不存在"); + return; + } + } + + public void WriteBelow(string below, string text) + { + StreamReader streamReader = new StreamReader(filePath); + string all = streamReader.ReadToEnd(); + streamReader.Close(); + int beginIndex = all.IndexOf(below, StringComparison.Ordinal); + if (beginIndex == -1) + { + Debug.LogError(filePath + "中没有找到字符串" + below); + return; + } + + int endIndex = all.LastIndexOf("\n", beginIndex + below.Length, StringComparison.Ordinal); + all = all.Substring(0, endIndex) + "\n" + text + "\n" + all.Substring(endIndex); + StreamWriter streamWriter = new StreamWriter(filePath); + streamWriter.Write(all); + streamWriter.Close(); + } + + public void Replace(string below, string newText) + { + StreamReader streamReader = new StreamReader(filePath); + string all = streamReader.ReadToEnd(); + streamReader.Close(); + int beginIndex = all.IndexOf(below, StringComparison.Ordinal); + if (beginIndex == -1) + { + Debug.LogError(filePath + "中没有找到字符串" + below); + return; + } + + all = all.Replace(below, newText); + StreamWriter streamWriter = new StreamWriter(filePath); + streamWriter.Write(all); + streamWriter.Close(); + } + + public void Dispose() + { + } + + public static void CopyAndReplaceDirectory(string srcPath, string dstPath) + { + if (Directory.Exists(dstPath)) + Directory.Delete(dstPath, true); + if (File.Exists(dstPath)) + File.Delete(dstPath); + + Directory.CreateDirectory(dstPath); + + foreach (var file in Directory.GetFiles(srcPath)) + File.Copy(file, Path.Combine(dstPath, Path.GetFileName(file))); + + foreach (var dir in Directory.GetDirectories(srcPath)) + CopyAndReplaceDirectory(dir, Path.Combine(dstPath, Path.GetFileName(dir))); + } + + + public static void DeleteFileBySuffix(string dir, string[] suffix) + { + if (!Directory.Exists(dir)) + { + return; + } + + foreach (var file in Directory.GetFiles(dir)) + { + foreach (var suffixName in suffix) + { + if (file.Contains(suffixName)) + { + File.Delete(file); + } + } + } + } + + public static string FilterFileByPrefix(string srcPath, string filterName) + { + if (!Directory.Exists(srcPath)) + { + return null; + } + + foreach (var dir in Directory.GetDirectories(srcPath)) + { + string fileName = Path.GetFileName(dir); + if (fileName.StartsWith(filterName)) + { + return Path.Combine(srcPath, Path.GetFileName(dir)); + } + } + + return null; + } + + public static string FilterFileBySuffix(string srcPath, string suffix) + { + if (!Directory.Exists(srcPath)) + { + return null; + } + + foreach (var dir in Directory.GetDirectories(srcPath)) + { + string fileName = Path.GetFileName(dir); + if (fileName.StartsWith(suffix)) + { + return Path.Combine(srcPath, Path.GetFileName(dir)); + } + } + + return null; + } + + public static FileInfo RecursionFilterFile(string dir, string fileName) + { + List fileInfoList = new List(); + Director(dir, fileInfoList); + foreach (FileInfo item in fileInfoList) + { + if (fileName.Equals(item.Name)) + { + return item; + } + } + + return null; + } + + public static void Director(string dir, List list) + { + DirectoryInfo d = new DirectoryInfo(dir); + FileInfo[] files = d.GetFiles(); + DirectoryInfo[] directs = d.GetDirectories(); + foreach (FileInfo f in files) + { + list.Add(f); + } + + foreach (DirectoryInfo dd in directs) + { + Director(dd.FullName, list); + } + } + } +} \ No newline at end of file diff --git a/Editor/TapFileHelper.cs.meta b/Editor/TapFileHelper.cs.meta new file mode 100644 index 0000000..3d34300 --- /dev/null +++ b/Editor/TapFileHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ba6aa94e0d93a4706a7d274735817de5 +timeCreated: 1617120740 \ No newline at end of file diff --git a/Editor/TapSDK.Core.Editor.asmdef b/Editor/TapSDK.Core.Editor.asmdef new file mode 100644 index 0000000..d940fda --- /dev/null +++ b/Editor/TapSDK.Core.Editor.asmdef @@ -0,0 +1,15 @@ +{ + "name": "TapSDK.Core.Editor", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/TapSDK.Core.Editor.asmdef.meta b/Editor/TapSDK.Core.Editor.asmdef.meta new file mode 100644 index 0000000..c3015fc --- /dev/null +++ b/Editor/TapSDK.Core.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 56f3da7a178484843974054bafe77e73 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/TapSDKCoreCompile.cs b/Editor/TapSDKCoreCompile.cs new file mode 100644 index 0000000..f69d6f4 --- /dev/null +++ b/Editor/TapSDKCoreCompile.cs @@ -0,0 +1,292 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.PackageManager; +using UnityEngine; +#if UNITY_IOS +using System; +using UnityEditor.iOS.Xcode; + +#endif + +namespace TapSDK.Core.Editor +{ + public static class TapSDKCoreCompile + { +#if UNITY_IOS + public static string GetProjPath(string path) + { + return PBXProject.GetPBXProjectPath(path); + } + + public static PBXProject ParseProjPath(string path) + { + var proj = new PBXProject(); + proj.ReadFromString(File.ReadAllText(path)); + return proj; + } + + public static string GetUnityFrameworkTarget(PBXProject proj) + { +#if UNITY_2019_3_OR_NEWER + string target = proj.GetUnityFrameworkTargetGuid(); + return target; +#endif + var unityPhoneTarget = proj.TargetGuidByName("Unity-iPhone"); + return unityPhoneTarget; + } + + public static string GetUnityTarget(PBXProject proj) + { +#if UNITY_2019_3_OR_NEWER + string target = proj.GetUnityMainTargetGuid(); + return target; +#endif + var unityPhoneTarget = proj.TargetGuidByName("Unity-iPhone"); + return unityPhoneTarget; + } + + + public static bool CheckTarget(string target) + { + return string.IsNullOrEmpty(target); + } + + public static string GetUnityPackagePath(string parentFolder, string unityPackageName) + { + var request = Client.List(true); + while (request.IsCompleted == false) + { + System.Threading.Thread.Sleep(100); + } + var pkgs = request.Result; + if (pkgs == null) + return ""; + foreach (var pkg in pkgs) + { + if (pkg.name == unityPackageName) + { + if (pkg.source == PackageSource.Local) + return pkg.resolvedPath; + else if (pkg.source == PackageSource.Embedded) + return pkg.resolvedPath; + else + { + return pkg.resolvedPath; + } + } + } + + return ""; + } + + public static bool HandlerIOSSetting(string path, string appDataPath, string resourceName, + string modulePackageName, + string moduleName, string[] bundleNames, string target, string projPath, PBXProject proj, string podSpecName = "") + { + + var resourcePath = Path.Combine(path, resourceName); + + var parentFolder = Directory.GetParent(appDataPath).FullName; + + Debug.Log($"ProjectFolder path:{parentFolder}" + " resourcePath: " + resourcePath + " parentFolder: " + parentFolder); + + if (Directory.Exists(resourcePath)) + { + Directory.Delete(resourcePath, true); + } + + var podSpecPath = Path.Combine(path + "/Pods", podSpecName); + //使用 cocospod 远程依赖 + if (podSpecName != null && podSpecName.Length > 0 && Directory.Exists(podSpecPath)) + { + resourcePath = Path.Combine(path + "/Pods", podSpecName + "/Frameworks"); + Debug.Log($"Find {moduleName} use pods resourcePath:{resourcePath}"); + } + else + { + Directory.CreateDirectory(resourcePath); + var remotePackagePath = GetUnityPackagePath(parentFolder, modulePackageName); + + var assetLocalPackagePath = TapFileHelper.FilterFileByPrefix(parentFolder + "/Assets/TapSDK/", moduleName); + + var localPackagePath = TapFileHelper.FilterFileByPrefix(parentFolder, moduleName); + + Debug.Log($"Find {moduleName} path: remote = {remotePackagePath} asset = {assetLocalPackagePath} local = {localPackagePath}"); + var tdsResourcePath = ""; + + if (!string.IsNullOrEmpty(remotePackagePath)) + { + tdsResourcePath = remotePackagePath; + } + else if (!string.IsNullOrEmpty(assetLocalPackagePath)) + { + tdsResourcePath = assetLocalPackagePath; + } + else if (!string.IsNullOrEmpty(localPackagePath)) + { + tdsResourcePath = localPackagePath; + } + + if (string.IsNullOrEmpty(tdsResourcePath)) + { + throw new Exception(string.Format("Can't find tdsResourcePath with module of : {0}", modulePackageName)); + } + + tdsResourcePath = $"{tdsResourcePath}/Plugins/iOS/Resource"; + + Debug.Log($"Find {moduleName} path:{tdsResourcePath}"); + + if (!Directory.Exists(tdsResourcePath)) + { + throw new Exception(string.Format("Can't Find {0}", tdsResourcePath)); + } + + TapFileHelper.CopyAndReplaceDirectory(tdsResourcePath, resourcePath); + } + foreach (var name in bundleNames) + { + var relativePath = GetRelativePath(Path.Combine(resourcePath, name), path); + var fileGuid = proj.AddFile(relativePath, relativePath, PBXSourceTree.Source); + proj.AddFileToBuild(target, fileGuid); + } + + File.WriteAllText(projPath, proj.WriteToString()); + return true; + } + + private static string GetRelativePath(string absolutePath, string rootPath) + { + if (Directory.Exists(rootPath) && !rootPath.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + { + rootPath += Path.AltDirectorySeparatorChar; + } + Uri aboslutePathUri = new Uri(absolutePath); + Uri rootPathUri = new Uri(rootPath); + var relateivePath = rootPathUri.MakeRelativeUri(aboslutePathUri).ToString(); + Debug.LogFormat($"[TapSDKCoreCompile] GetRelativePath absolutePath:{absolutePath} rootPath:{rootPath} relateivePath:{relateivePath} "); + return relateivePath; + } + + public static bool HandlerPlist(string pathToBuildProject, string infoPlistPath, bool macos = false) + { + // #if UNITY_2020_1_OR_NEWER + // var macosXCodePlistPath = + // $"{pathToBuildProject}/{PlayerSettings.productName}/Info.plist"; + // #elif UNITY_2019_1_OR_NEWER + // var macosXCodePlistPath = + // $"{Path.GetDirectoryName(pathToBuildProject)}/{PlayerSettings.productName}/Info.plist"; + // #endif + + string plistPath; + + if (pathToBuildProject.EndsWith(".app")) + { + plistPath = $"{pathToBuildProject}/Contents/Info.plist"; + } + else + { + var macosXCodePlistPath = + $"{Path.GetDirectoryName(pathToBuildProject)}/{PlayerSettings.productName}/Info.plist"; + if (!File.Exists(macosXCodePlistPath)) + { + macosXCodePlistPath = $"{pathToBuildProject}/{PlayerSettings.productName}/Info.plist"; + } + + plistPath = !macos + ? pathToBuildProject + "/Info.plist" + : macosXCodePlistPath; + } + + Debug.Log($"plist path:{plistPath}"); + + var plist = new PlistDocument(); + plist.ReadFromString(File.ReadAllText(plistPath)); + var rootDic = plist.root; + + var items = new List + { + "tapsdk", + "tapiosdk", + "taptap" + }; + + if (!(rootDic["LSApplicationQueriesSchemes"] is PlistElementArray plistElementList)) + { + plistElementList = rootDic.CreateArray("LSApplicationQueriesSchemes"); + } + + string listData = ""; + foreach (var item in plistElementList.values) { + if ( item is PlistElementString ){ + listData += item.AsString() + ";"; + } + } + foreach (var t in items) + { + if (!listData.Contains(t + ";")) { + plistElementList.AddString(t); + } + } + + if (string.IsNullOrEmpty(infoPlistPath)) return false; + var dic = (Dictionary)Plist.readPlist(infoPlistPath); + var taptapId = ""; + + foreach (var item in dic) + { + if (item.Key.Equals("taptap")) + { + var taptapDic = (Dictionary)item.Value; + foreach (var taptapItem in taptapDic.Where(taptapItem => taptapItem.Key.Equals("client_id"))) + { + taptapId = (string)taptapItem.Value; + } + } + else + { + rootDic.SetString(item.Key, item.Value.ToString()); + } + } + + //添加url + var dict = plist.root.AsDict(); + if (!(dict["CFBundleURLTypes"] is PlistElementArray array)) + { + array = dict.CreateArray("CFBundleURLTypes"); + } + + if (!macos) + { + var dict2 = array.AddDict(); + dict2.SetString("CFBundleURLName", "TapTap"); + var array2 = dict2.CreateArray("CFBundleURLSchemes"); + array2.AddString($"tt{taptapId}"); + } + else + { + var dict2 = array.AddDict(); + dict2.SetString("CFBundleURLName", "TapWeb"); + var array2 = dict2.CreateArray("CFBundleURLSchemes"); + array2.AddString($"open-taptap-{taptapId}"); + } + + Debug.Log("TapSDK change plist Success"); + File.WriteAllText(plistPath, plist.WriteToString()); + return true; + } + + public static string GetValueFromPlist(string infoPlistPath, string key) + { + if (infoPlistPath == null) + { + return null; + } + + var dic = (Dictionary)Plist.readPlist(infoPlistPath); + return (from item in dic where item.Key.Equals(key) select (string)item.Value).FirstOrDefault(); + } +#endif + } +} \ No newline at end of file diff --git a/Editor/TapSDKCoreCompile.cs.meta b/Editor/TapSDKCoreCompile.cs.meta new file mode 100644 index 0000000..ed05756 --- /dev/null +++ b/Editor/TapSDKCoreCompile.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ec6f5ced361da4c9ba6c09d4e2dba5fd +timeCreated: 1617120740 \ No newline at end of file diff --git a/Editor/TapSDKCoreIOSProcessor.cs b/Editor/TapSDKCoreIOSProcessor.cs new file mode 100644 index 0000000..3f74049 --- /dev/null +++ b/Editor/TapSDKCoreIOSProcessor.cs @@ -0,0 +1,74 @@ +using System.IO; +using UnityEditor; +# if UNITY_IOS +using UnityEditor.Callbacks; +using UnityEditor.iOS.Xcode; +#endif +using UnityEngine; + +namespace TapSDK.Core.Editor +{ +# if UNITY_IOS + public static class TapCommonIOSProcessor + { + // 添加标签,unity导出工程后自动执行该函数 + [PostProcessBuild(99)] + public static void OnPostprocessBuild(BuildTarget buildTarget, string path) + { + if (buildTarget != BuildTarget.iOS) return; + + // 获得工程路径 + var projPath = TapSDKCoreCompile.GetProjPath(path); + var proj = TapSDKCoreCompile.ParseProjPath(projPath); + var target = TapSDKCoreCompile.GetUnityTarget(proj); + var unityFrameworkTarget = TapSDKCoreCompile.GetUnityFrameworkTarget(proj); + + if (TapSDKCoreCompile.CheckTarget(target)) + { + Debug.LogError("Unity-iPhone is NUll"); + return; + } + + proj.AddBuildProperty(target, "OTHER_LDFLAGS", "-ObjC"); + proj.AddBuildProperty(unityFrameworkTarget, "OTHER_LDFLAGS", "-ObjC"); + + proj.SetBuildProperty(target, "ENABLE_BITCODE", "NO"); + proj.SetBuildProperty(target, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); + proj.SetBuildProperty(target, "SWIFT_VERSION", "5.0"); + proj.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); + + proj.SetBuildProperty(unityFrameworkTarget, "ENABLE_BITCODE", "NO"); + proj.SetBuildProperty(unityFrameworkTarget, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "NO"); + proj.SetBuildProperty(unityFrameworkTarget, "SWIFT_VERSION", "5.0"); + proj.SetBuildProperty(unityFrameworkTarget, "CLANG_ENABLE_MODULES", "YES"); + + proj.AddFrameworkToProject(unityFrameworkTarget, "MobileCoreServices.framework", false); + proj.AddFrameworkToProject(unityFrameworkTarget, "WebKit.framework", false); + proj.AddFrameworkToProject(unityFrameworkTarget, "Security.framework", false); + proj.AddFrameworkToProject(unityFrameworkTarget, "SystemConfiguration.framework", false); + proj.AddFrameworkToProject(unityFrameworkTarget, "CoreTelephony.framework", false); + proj.AddFrameworkToProject(unityFrameworkTarget, "SystemConfiguration.framework", false); + + proj.AddFileToBuild(unityFrameworkTarget, + proj.AddFile("usr/lib/libc++.tbd", "libc++.tbd", PBXSourceTree.Sdk)); + + proj.AddFileToBuild(unityFrameworkTarget, + proj.AddFile("usr/lib/libsqlite3.tbd", "libsqlite3.tbd", PBXSourceTree.Sdk)); + + // if (TapSDKCoreCompile.HandlerIOSSetting(path, + // Application.dataPath, + // "TapCommonResource", + // "com.taptap.sdk.core", + // "TapSDKCore", + // new[] {"TapCommonResource.bundle"}, + // target, projPath, proj)) + // { + // Debug.Log("TapCommon add Bundle Success!"); + // return; + // } + + // Debug.LogError("TapCommon add Bundle Failed!"); + } + } +#endif +} \ No newline at end of file diff --git a/Editor/TapSDKCoreIOSProcessor.cs.meta b/Editor/TapSDKCoreIOSProcessor.cs.meta new file mode 100644 index 0000000..8ee418f --- /dev/null +++ b/Editor/TapSDKCoreIOSProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b61d97b2d6da94511a1e92afb1519d42 +timeCreated: 1617120740 \ No newline at end of file diff --git a/Editor/UI.meta b/Editor/UI.meta new file mode 100644 index 0000000..152a7ed --- /dev/null +++ b/Editor/UI.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6301720de19974c94b52839ed842dc3f +folderAsset: yes +timeCreated: 1533042733 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UI/ScrollViewEditor.cs b/Editor/UI/ScrollViewEditor.cs new file mode 100644 index 0000000..0183a1e --- /dev/null +++ b/Editor/UI/ScrollViewEditor.cs @@ -0,0 +1,267 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) AillieoTech. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace TapSDK.UI.AillieoTech +{ + using System; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEditor.UI; + using UnityEngine; + using UnityEngine.UI; + + [CustomEditor(typeof(ScrollView))] + public class ScrollViewEditor : ScrollRectEditor + { + private const string bgPath = "UI/Skin/Background.psd"; + private const string spritePath = "UI/Skin/UISprite.psd"; + private const string maskPath = "UI/Skin/UIMask.psd"; + private static Color panelColor = new Color(1f, 1f, 1f, 0.392f); + private static Color defaultSelectableColor = new Color(1f, 1f, 1f, 1f); + private static Vector2 thinElementSize = new Vector2(160f, 20f); + private static Action PlaceUIElementRoot; + + private SerializedProperty itemTemplate; + private SerializedProperty poolSize; + private SerializedProperty defaultItemSize; + private SerializedProperty layoutType; + + private GUIStyle cachedCaption; + + private GUIStyle caption + { + get + { + if (this.cachedCaption == null) + { + this.cachedCaption = new GUIStyle { richText = true, alignment = TextAnchor.MiddleCenter }; + } + + return this.cachedCaption; + } + } + + public override void OnInspectorGUI() + { + this.serializedObject.Update(); + + EditorGUILayout.BeginVertical("box"); + EditorGUILayout.LabelField("Additional configs", this.caption); + EditorGUILayout.Space(); + this.DrawConfigInfo(); + this.serializedObject.ApplyModifiedProperties(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical("box"); + EditorGUILayout.LabelField("For original ScrollRect", this.caption); + EditorGUILayout.Space(); + base.OnInspectorGUI(); + EditorGUILayout.EndVertical(); + } + + protected static void InternalAddScrollView(MenuCommand menuCommand) + where T : ScrollView + { + GetPrivateMethodByReflection(); + + GameObject root = CreateUIElementRoot(typeof(T).Name, new Vector2(200, 200)); + PlaceUIElementRoot?.Invoke(root, menuCommand); + + GameObject viewport = CreateUIObject("Viewport", root); + GameObject content = CreateUIObject("Content", viewport); + + var parent = menuCommand.context as GameObject; + if (parent != null) + { + root.transform.SetParent(parent.transform, false); + } + + Selection.activeGameObject = root; + + GameObject hScrollbar = CreateScrollbar(); + hScrollbar.name = "Scrollbar Horizontal"; + hScrollbar.transform.SetParent(root.transform, false); + RectTransform hScrollbarRT = hScrollbar.GetComponent(); + hScrollbarRT.anchorMin = Vector2.zero; + hScrollbarRT.anchorMax = Vector2.right; + hScrollbarRT.pivot = Vector2.zero; + hScrollbarRT.sizeDelta = new Vector2(0, hScrollbarRT.sizeDelta.y); + + GameObject vScrollbar = CreateScrollbar(); + vScrollbar.name = "Scrollbar Vertical"; + vScrollbar.transform.SetParent(root.transform, false); + vScrollbar.GetComponent().SetDirection(Scrollbar.Direction.BottomToTop, true); + RectTransform vScrollbarRT = vScrollbar.GetComponent(); + vScrollbarRT.anchorMin = Vector2.right; + vScrollbarRT.anchorMax = Vector2.one; + vScrollbarRT.pivot = Vector2.one; + vScrollbarRT.sizeDelta = new Vector2(vScrollbarRT.sizeDelta.x, 0); + + RectTransform viewportRect = viewport.GetComponent(); + viewportRect.anchorMin = Vector2.zero; + viewportRect.anchorMax = Vector2.one; + viewportRect.sizeDelta = Vector2.zero; + viewportRect.pivot = Vector2.up; + + RectTransform contentRect = content.GetComponent(); + contentRect.anchorMin = Vector2.up; + contentRect.anchorMax = Vector2.one; + contentRect.sizeDelta = new Vector2(0, 300); + contentRect.pivot = Vector2.up; + + ScrollView scrollRect = root.AddComponent(); + scrollRect.content = contentRect; + scrollRect.viewport = viewportRect; + scrollRect.horizontalScrollbar = hScrollbar.GetComponent(); + scrollRect.verticalScrollbar = vScrollbar.GetComponent(); + scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + scrollRect.horizontalScrollbarSpacing = -3; + scrollRect.verticalScrollbarSpacing = -3; + + Image rootImage = root.AddComponent(); + rootImage.sprite = AssetDatabase.GetBuiltinExtraResource(bgPath); + rootImage.type = Image.Type.Sliced; + rootImage.color = panelColor; + + Mask viewportMask = viewport.AddComponent(); + viewportMask.showMaskGraphic = false; + + Image viewportImage = viewport.AddComponent(); + viewportImage.sprite = AssetDatabase.GetBuiltinExtraResource(maskPath); + viewportImage.type = Image.Type.Sliced; + } + + protected override void OnEnable() + { + base.OnEnable(); + + this.itemTemplate = this.serializedObject.FindProperty("itemTemplate"); + this.poolSize = this.serializedObject.FindProperty("poolSize"); + this.defaultItemSize = this.serializedObject.FindProperty("defaultItemSize"); + this.layoutType = this.serializedObject.FindProperty("layoutType"); + } + + protected virtual void DrawConfigInfo() + { + EditorGUILayout.PropertyField(this.itemTemplate); + EditorGUILayout.PropertyField(this.poolSize); + EditorGUILayout.PropertyField(this.defaultItemSize); + this.layoutType.intValue = (int)(ScrollView.ItemLayoutType)EditorGUILayout.EnumPopup("layoutType", (ScrollView.ItemLayoutType)this.layoutType.intValue); + } + + [MenuItem("GameObject/UI/DynamicScrollView", false, 90)] + private static void AddScrollView(MenuCommand menuCommand) + { + InternalAddScrollView(menuCommand); + } + + private static GameObject CreateScrollbar() + { + // Create GOs Hierarchy + GameObject scrollbarRoot = CreateUIElementRoot("Scrollbar", thinElementSize); + GameObject sliderArea = CreateUIObject("Sliding Area", scrollbarRoot); + GameObject handle = CreateUIObject("Handle", sliderArea); + + Image bgImage = scrollbarRoot.AddComponent(); + bgImage.sprite = AssetDatabase.GetBuiltinExtraResource(bgPath); + bgImage.type = Image.Type.Sliced; + bgImage.color = defaultSelectableColor; + + Image handleImage = handle.AddComponent(); + handleImage.sprite = AssetDatabase.GetBuiltinExtraResource(spritePath); + handleImage.type = Image.Type.Sliced; + handleImage.color = defaultSelectableColor; + + RectTransform sliderAreaRect = sliderArea.GetComponent(); + sliderAreaRect.sizeDelta = new Vector2(-20, -20); + sliderAreaRect.anchorMin = Vector2.zero; + sliderAreaRect.anchorMax = Vector2.one; + + RectTransform handleRect = handle.GetComponent(); + handleRect.sizeDelta = new Vector2(20, 20); + + Scrollbar scrollbar = scrollbarRoot.AddComponent(); + scrollbar.handleRect = handleRect; + scrollbar.targetGraphic = handleImage; + SetDefaultColorTransitionValues(scrollbar); + + return scrollbarRoot; + } + + private static GameObject CreateUIElementRoot(string name, Vector2 size) + { + var child = new GameObject(name); + RectTransform rectTransform = child.AddComponent(); + rectTransform.sizeDelta = size; + return child; + } + + private static GameObject CreateUIObject(string name, GameObject parent) + { + var go = new GameObject(name); + go.AddComponent(); + SetParentAndAlign(go, parent); + return go; + } + + private static void SetParentAndAlign(GameObject child, GameObject parent) + { + if (parent == null) + { + return; + } + + child.transform.SetParent(parent.transform, false); + SetLayerRecursively(child, parent.layer); + } + + private static void SetLayerRecursively(GameObject go, int layer) + { + go.layer = layer; + Transform t = go.transform; + for (var i = 0; i < t.childCount; i++) + { + SetLayerRecursively(t.GetChild(i).gameObject, layer); + } + } + + private static void SetDefaultColorTransitionValues(Selectable slider) + { + ColorBlock colors = slider.colors; + colors.highlightedColor = new Color(0.882f, 0.882f, 0.882f); + colors.pressedColor = new Color(0.698f, 0.698f, 0.698f); + colors.disabledColor = new Color(0.521f, 0.521f, 0.521f); + } + + private static void GetPrivateMethodByReflection() + { + if (PlaceUIElementRoot == null) + { + Assembly uiEditorAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(asm => asm.GetName().Name == "UnityEditor.UI"); + if (uiEditorAssembly != null) + { + Type menuOptionType = uiEditorAssembly.GetType("UnityEditor.UI.MenuOptions"); + if (menuOptionType != null) + { + MethodInfo miPlaceUIElementRoot = menuOptionType.GetMethod( + "PlaceUIElementRoot", + BindingFlags.NonPublic | BindingFlags.Static); + if (miPlaceUIElementRoot != null) + { + PlaceUIElementRoot = Delegate.CreateDelegate( + typeof(Action), + miPlaceUIElementRoot) + as Action; + } + } + } + } + } + } +} diff --git a/Editor/UI/ScrollViewEditor.cs.meta b/Editor/UI/ScrollViewEditor.cs.meta new file mode 100644 index 0000000..4322453 --- /dev/null +++ b/Editor/UI/ScrollViewEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c20879c71b49a4b32b4170efe40af02c +timeCreated: 1533042733 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UI/ScrollViewExEditor.cs b/Editor/UI/ScrollViewExEditor.cs new file mode 100644 index 0000000..bcc0a39 --- /dev/null +++ b/Editor/UI/ScrollViewExEditor.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) AillieoTech. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace TapSDK.UI.AillieoTech +{ + using UnityEditor; + + [CustomEditor(typeof(ScrollViewEx))] + public class ScrollViewExEditor : ScrollViewEditor + { + private SerializedProperty pageSize; + + protected override void OnEnable() + { + base.OnEnable(); + this.pageSize = this.serializedObject.FindProperty("pageSize"); + } + + protected override void DrawConfigInfo() + { + base.DrawConfigInfo(); + EditorGUILayout.PropertyField(this.pageSize); + } + + [MenuItem("GameObject/UI/DynamicScrollViewEx", false, 90)] + private static void AddScrollViewEx(MenuCommand menuCommand) + { + InternalAddScrollView(menuCommand); + } + } +} diff --git a/Editor/UI/ScrollViewExEditor.cs.meta b/Editor/UI/ScrollViewExEditor.cs.meta new file mode 100644 index 0000000..a7c8d06 --- /dev/null +++ b/Editor/UI/ScrollViewExEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0d7c4e920d9c4441a83c3d500596fb75 +timeCreated: 1533042733 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UI/TapSDK.UI.Editor.asmdef b/Editor/UI/TapSDK.UI.Editor.asmdef new file mode 100644 index 0000000..c19b73f --- /dev/null +++ b/Editor/UI/TapSDK.UI.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "TapSDK.UI.Editor", + "references": [ + "GUID:7d5ef2062f3704e1ab74aac0e4d5a1a7" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/UI/TapSDK.UI.Editor.asmdef.meta b/Editor/UI/TapSDK.UI.Editor.asmdef.meta new file mode 100644 index 0000000..1a878a9 --- /dev/null +++ b/Editor/UI/TapSDK.UI.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d9925423e828d479c9063ea882f31e06 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile.meta b/Mobile.meta new file mode 100644 index 0000000..1435211 --- /dev/null +++ b/Mobile.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6838d5bad18c0416688fd347e4b283d9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Editor.meta b/Mobile/Editor.meta new file mode 100644 index 0000000..c412aaf --- /dev/null +++ b/Mobile/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 17753ed93cd314b168087b8f75b02b00 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Editor/NativeDependencies.xml b/Mobile/Editor/NativeDependencies.xml new file mode 100644 index 0000000..07c1fe7 --- /dev/null +++ b/Mobile/Editor/NativeDependencies.xml @@ -0,0 +1,16 @@ + + + + + https://repo.maven.apache.org/maven2 + + + + + + https://github.com/CocoaPods/Specs.git + + + + + \ No newline at end of file diff --git a/Mobile/Editor/NativeDependencies.xml.meta b/Mobile/Editor/NativeDependencies.xml.meta new file mode 100644 index 0000000..5b8b69b --- /dev/null +++ b/Mobile/Editor/NativeDependencies.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b969d6e811d804faca9742abe7c51db0 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Editor/TapCommonMobileProcessBuild.cs b/Mobile/Editor/TapCommonMobileProcessBuild.cs new file mode 100644 index 0000000..285bb2f --- /dev/null +++ b/Mobile/Editor/TapCommonMobileProcessBuild.cs @@ -0,0 +1,20 @@ +using System; +using UnityEditor.Build.Reporting; +using TapSDK.Core.Editor; + +namespace TapSDK.Core.Mobile.Editor { + public class TapCommonMobileProcessBuild : SDKLinkProcessBuild { + public override int callbackOrder => 0; + + public override string LinkPath => "TapSDK/Core/link.xml"; + + public override LinkedAssembly[] LinkedAssemblies => new LinkedAssembly[] { + new LinkedAssembly { Fullname = "TapSDK.Core.Runtime" }, + new LinkedAssembly { Fullname = "TapSDK.Core.Mobile.Runtime" } + }; + + public override Func IsTargetPlatform => (report) => { + return BuildTargetUtils.IsSupportMobile(report.summary.platform); + }; + } +} diff --git a/Mobile/Editor/TapCommonMobileProcessBuild.cs.meta b/Mobile/Editor/TapCommonMobileProcessBuild.cs.meta new file mode 100644 index 0000000..e77bf37 --- /dev/null +++ b/Mobile/Editor/TapCommonMobileProcessBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8e298a37fded43bb96e28c921c396d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef b/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef new file mode 100644 index 0000000..c8cd857 --- /dev/null +++ b/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "TapSDK.Core.Mobile.Editor", + "references": [ + "GUID:56f3da7a178484843974054bafe77e73" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef.meta b/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef.meta new file mode 100644 index 0000000..a2986f7 --- /dev/null +++ b/Mobile/Editor/TapSDK.Core.Mobile.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4f379049292174fb88b6a19b4c7fc83b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime.meta b/Mobile/Runtime.meta new file mode 100644 index 0000000..625a7f1 --- /dev/null +++ b/Mobile/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 401790f14411442e6b4d30acc9ec31c6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/AndroidNativeWrapper.cs b/Mobile/Runtime/AndroidNativeWrapper.cs new file mode 100644 index 0000000..72be03a --- /dev/null +++ b/Mobile/Runtime/AndroidNativeWrapper.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; +using System.Collections; +using TapSDK.Core; +using TapSDK.Core.Internal; +using System.Collections.Generic; + +namespace TapSDK.Core.Mobile{ + internal class AndroidNativeWrapper + { + private static AndroidJavaClass tapTapEventClass; + + public static void RegisterDynamicProperties(Func callback) + { + tapTapEventClass = new AndroidJavaClass("com.taptap.sdk.core.TapTapEvent"); + AndroidJavaProxy dynamicPropertiesProxy = new TapEventDynamicPropertiesProxy(callback); + tapTapEventClass.CallStatic("registerDynamicProperties", dynamicPropertiesProxy); + } + + private class TapEventDynamicPropertiesProxy : AndroidJavaProxy + { + private Func callback; + + public TapEventDynamicPropertiesProxy(Func callback) + : base("com.taptap.sdk.core.TapTapEvent$TapEventDynamicProperties") + { + this.callback = callback; + } + + public AndroidJavaObject getDynamicProperties() + { + try + { + string json = callback(); + if (!string.IsNullOrEmpty(json)) + { + return new AndroidJavaObject("org.json.JSONObject", json); + } + } + catch (Exception e) + { + Debug.LogError("Failed to get dynamic properties: " + e.Message); + } + return null; + } + } + + } +} \ No newline at end of file diff --git a/Mobile/Runtime/AndroidNativeWrapper.cs.meta b/Mobile/Runtime/AndroidNativeWrapper.cs.meta new file mode 100644 index 0000000..20467e5 --- /dev/null +++ b/Mobile/Runtime/AndroidNativeWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da60c9deeb9b14586a7e4a0eec603fbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/Bridge.cs b/Mobile/Runtime/Bridge.cs new file mode 100644 index 0000000..c3e3bd6 --- /dev/null +++ b/Mobile/Runtime/Bridge.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; + +namespace TapSDK.Core +{ + public class EngineBridge + { + private static volatile EngineBridge _sInstance; + + private readonly IBridge _bridge; + + private static readonly object Locker = new object(); + + public static EngineBridge GetInstance() + { + lock (Locker) + { + if (_sInstance == null) + { + _sInstance = new EngineBridge(); + } + } + + return _sInstance; + } + + private EngineBridge() + { + if (Platform.IsAndroid()) + { + _bridge = BridgeAndroid.GetInstance(); + } + else if (Platform.IsIOS()) + { + _bridge = BridgeIOS.GetInstance(); + } + } + + public void Register(string serviceClzName, string serviceImplName) + { + _bridge?.Register(serviceClzName, serviceImplName); + } + + public void CallHandler(Command command) + { + _bridge?.Call(command); + } + + public string CallWithReturnValue(Command command, Action action = null) + { + return _bridge?.CallWithReturnValue(command, action); + } + + public void CallHandler(Command command, Action action) + { + _bridge?.Call(command, action); + } + + public Task Emit(Command command) + { + var tcs = new TaskCompletionSource(); + CallHandler(command, result => + { + tcs.TrySetResult(result); + }); + return tcs.Task; + } + + public static bool CheckResult(Result result) + { + if (result == null) + { + return false; + } + + if (result.code != Result.RESULT_SUCCESS) + { + return false; + } + + return !string.IsNullOrEmpty(result.content); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/Bridge.cs.meta b/Mobile/Runtime/Bridge.cs.meta new file mode 100644 index 0000000..7bd4951 --- /dev/null +++ b/Mobile/Runtime/Bridge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 168155d160dc0448bb9f57e609c284af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/BridgeAndroid.cs b/Mobile/Runtime/BridgeAndroid.cs new file mode 100644 index 0000000..1305f3f --- /dev/null +++ b/Mobile/Runtime/BridgeAndroid.cs @@ -0,0 +1,73 @@ +using System; +using UnityEngine; + +namespace TapSDK.Core +{ + public class BridgeAndroid : IBridge + { + private string bridgeJavaClz = "com.taptap.sdk.kit.internal.enginebridge.EngineBridge"; + + private string instanceField = "INSTANCE"; + + private string registerHandlerMethod = "registerHandler"; + + private string callHandlerMethod = "execCommand"; + + private string callHandlerAsyncMethod = "execCommandAsync"; + + private string initMethod = "init"; + + private string registerMethod = "registerService"; + + private readonly AndroidJavaObject _mAndroidBridge; + + private static readonly BridgeAndroid SInstance = new BridgeAndroid(); + + public static BridgeAndroid GetInstance() + { + return SInstance; + } + + private BridgeAndroid() + { + var mCurrentActivity = + new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic("currentActivity"); + _mAndroidBridge = new AndroidJavaClass(bridgeJavaClz).GetStatic(instanceField); + _mAndroidBridge.Call(initMethod, mCurrentActivity); + } + + public void Register(string serviceClzName, string serviceImplName) + { + if (_mAndroidBridge == null) + { + return; + } + + try + { + var serviceClass = new AndroidJavaClass(serviceClzName); + var serviceImpl = new AndroidJavaObject(serviceImplName); + _mAndroidBridge.Call(registerMethod, serviceClass, serviceImpl); + } + catch (Exception e) + { + Debug.Log("register Failed:" + e); + // + } + } + + public void Call(Command command, Action action) + { + _mAndroidBridge?.Call(callHandlerMethod, command.ToJSON(), new BridgeCallback(action)); + } + + public void Call(Command command) + { + _mAndroidBridge?.Call(callHandlerMethod, command.ToJSON(), null); + } + public string CallWithReturnValue(Command command, Action action) + { + return _mAndroidBridge?.Call(callHandlerAsyncMethod, command.ToJSON()); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/BridgeAndroid.cs.meta b/Mobile/Runtime/BridgeAndroid.cs.meta new file mode 100644 index 0000000..eb3405c --- /dev/null +++ b/Mobile/Runtime/BridgeAndroid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f691a68724f14b63b7c60ac2f967488 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/BridgeCallback.cs b/Mobile/Runtime/BridgeCallback.cs new file mode 100644 index 0000000..ad053b9 --- /dev/null +++ b/Mobile/Runtime/BridgeCallback.cs @@ -0,0 +1,35 @@ +using System; +using UnityEngine; +using System.Threading; +using TapSDK.Core.Internal.Utils; + +namespace TapSDK.Core +{ + + public class BridgeCallback : AndroidJavaProxy + { + Action callback; + + public BridgeCallback(Action action) : + base(new AndroidJavaClass("com.taptap.sdk.kit.internal.enginebridge.EngineBridgeCallback")) + { + this.callback = action; + } + + public override AndroidJavaObject Invoke(string method, object[] args) + { + if (method.Equals("onResult")) + { + if (args[0] is string) + { + string result = (string)(args[0]); + TapLoom.QueueOnMainThread(() => + { + callback(new Result(result)); + }); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/BridgeCallback.cs.meta b/Mobile/Runtime/BridgeCallback.cs.meta new file mode 100644 index 0000000..3735ead --- /dev/null +++ b/Mobile/Runtime/BridgeCallback.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5ce170f9f3e64033909f7513e067032 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/BridgeIOS.cs b/Mobile/Runtime/BridgeIOS.cs new file mode 100644 index 0000000..cb0eb22 --- /dev/null +++ b/Mobile/Runtime/BridgeIOS.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace TapSDK.Core +{ + public class BridgeIOS : IBridge + { + private static readonly BridgeIOS SInstance = new BridgeIOS(); + + private readonly ConcurrentDictionary> dic; + + public static BridgeIOS GetInstance() + { + return SInstance; + } + + private BridgeIOS() + { + dic = new ConcurrentDictionary>(); + } + + private ConcurrentDictionary> GetConcurrentDictionary() + { + return dic; + } + + private delegate void EngineBridgeDelegate(string result); + + [AOT.MonoPInvokeCallbackAttribute(typeof(EngineBridgeDelegate))] + static void engineBridgeDelegate(string resultJson) + { + var result = new Result(resultJson); + + var actionDic = GetInstance().GetConcurrentDictionary(); + + Action action = null; + + if (actionDic != null && actionDic.ContainsKey(result.callbackId)) + { + action = actionDic[result.callbackId]; + } + + if (action != null) + { + action(result); + if (result.onceTime && BridgeIOS.GetInstance().GetConcurrentDictionary() + .TryRemove(result.callbackId, out Action outAction)) + { + Debug.Log($"TapSDK resolved current Action:{result.callbackId}"); + } + } + + Debug.Log($"TapSDK iOS BridgeAction last Count:{BridgeIOS.GetInstance().GetConcurrentDictionary().Count}"); + } + + + + public void Register(string serviceClz, string serviceImp) + { + //IOS无需注册 + } + + public void Call(Command command) + { +#if UNITY_IOS + callHandler(command.ToJSON()); +#endif + } + + public void Call(Command command, Action action) + { + if (!command.withCallback || string.IsNullOrEmpty(command.callbackId)) return; + if (!dic.ContainsKey(command.callbackId)) + { + dic.GetOrAdd(command.callbackId, action); + } +#if UNITY_IOS + registerHandler(command.ToJSON(), engineBridgeDelegate); +#endif + } + + public string CallWithReturnValue(Command command, Action action) + { + if (command.callbackId != null && !dic.ContainsKey(command.callbackId)) + { + dic.GetOrAdd(command.callbackId, action); + } +#if UNITY_IOS + if (action == null) + { + return callWithReturnValue(command.ToJSON(), null); + } else { + return callWithReturnValue(command.ToJSON(), engineBridgeDelegate); + } +#else + return null; +#endif + } + +#if UNITY_IOS + [DllImport("__Internal")] + private static extern string callWithReturnValue(string command, EngineBridgeDelegate engineBridgeDelegate); + + [DllImport("__Internal")] + private static extern void callHandler(string command); + + [DllImport("__Internal")] + private static extern void registerHandler(string command, EngineBridgeDelegate engineBridgeDelegate); +#endif + } +} \ No newline at end of file diff --git a/Mobile/Runtime/BridgeIOS.cs.meta b/Mobile/Runtime/BridgeIOS.cs.meta new file mode 100644 index 0000000..d14abff --- /dev/null +++ b/Mobile/Runtime/BridgeIOS.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a02205ed9346141f29e5729b12bae65a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/Command.cs b/Mobile/Runtime/Command.cs new file mode 100644 index 0000000..0bc5d37 --- /dev/null +++ b/Mobile/Runtime/Command.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace TapSDK.Core +{ + public class Command + { + [SerializeField] public string service; + [SerializeField] public string method; + [SerializeField] public string args; + [SerializeField] public bool withCallback; + [SerializeField] public string callbackId; + [SerializeField] public bool disposable; + + public Command() + { + + } + + public Command(string json) + { + JsonUtility.FromJsonOverwrite(json, this); + } + + public string ToJSON() + { + return JsonUtility.ToJson(this); + } + + public Command(string service, string method, bool callback, Dictionary dic) + { + this.args = dic == null ? null : Json.Serialize(dic); + this.service = service; + this.method = method; + this.withCallback = callback; + this.callbackId = this.withCallback ? TapUUID.UUID() : null; + } + + public Command(string service, string method, bool callback, bool onceTime, Dictionary dic) + { + this.args = dic == null ? null : Json.Serialize(dic); + this.service = service; + this.method = method; + this.withCallback = callback; + this.callbackId = this.withCallback ? TapUUID.UUID() : null; + this.disposable = onceTime; + } + + public class Builder + { + private string service; + + private string method; + + private bool withCallback; + + private string callbackId; + + private bool disposable; + + private Dictionary args; + + public Builder() + { + + } + + public Builder Service(string service) + { + this.service = service; + return this; + } + + public Builder Method(string method) + { + this.method = method; + return this; + } + + public Builder OnceTime(bool onceTime) + { + this.disposable = onceTime; + return this; + } + + public Builder Args(Dictionary dic) + { + this.args = dic; + return this; + } + + public Builder Args(string key, object value) + { + if (this.args == null) + { + this.args = new Dictionary(); + } + if(value is Dictionary) + { + value = Json.Serialize(value); + } + this.args.Add(key, value); + return this; + } + + public Builder Callback(bool callback) + { + this.withCallback = callback; + this.callbackId = this.withCallback ? TapUUID.UUID() : null; + return this; + } + + public Command CommandBuilder() + { + return new Command(this.service, this.method, this.withCallback, this.disposable, this.args); + } + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/Command.cs.meta b/Mobile/Runtime/Command.cs.meta new file mode 100644 index 0000000..02c3ee4 --- /dev/null +++ b/Mobile/Runtime/Command.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf7276ae3f7ff45f9b1f35d69ede7afa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/Constants.cs b/Mobile/Runtime/Constants.cs new file mode 100644 index 0000000..41e4697 --- /dev/null +++ b/Mobile/Runtime/Constants.cs @@ -0,0 +1,9 @@ +namespace TapSDK.Core +{ + public static class Constants + { + public const string VersionKey = "Engine-Version"; + + public const string PlatformKey = "Engine-Platform"; + } +} \ No newline at end of file diff --git a/Mobile/Runtime/Constants.cs.meta b/Mobile/Runtime/Constants.cs.meta new file mode 100644 index 0000000..699001f --- /dev/null +++ b/Mobile/Runtime/Constants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93d344834789c4be3973b920f2f3db9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/EngineBridgeInitializer.cs b/Mobile/Runtime/EngineBridgeInitializer.cs new file mode 100644 index 0000000..d59e5dd --- /dev/null +++ b/Mobile/Runtime/EngineBridgeInitializer.cs @@ -0,0 +1,33 @@ +using UnityEngine; +using TapSDK.Core.Internal; + +namespace TapSDK.Core.Mobile +{ + public static class EngineBridgeInitializer + { + private static bool isInitialized = false; + private const string SERVICE_NAME = "BridgeCoreService"; + + public static void Initialize() + { + if (!isInitialized) + { + Debug.Log("Initializing EngineBridge"); + + // TODO: android 注册桥接 + // #if UNITY_ANDROID + EngineBridge.GetInstance().Register( + "com.taptap.sdk.core.unity.BridgeCoreService", + "com.taptap.sdk.core.unity.BridgeCoreServiceImpl"); + // #endif + + isInitialized = true; + } + } + + public static Command.Builder GetBridgeServer() + { + return new Command.Builder().Service(SERVICE_NAME); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/EngineBridgeInitializer.cs.meta b/Mobile/Runtime/EngineBridgeInitializer.cs.meta new file mode 100644 index 0000000..701dbad --- /dev/null +++ b/Mobile/Runtime/EngineBridgeInitializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 75ce492acd08b47a589d97f2eb9373ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/IBridge.cs b/Mobile/Runtime/IBridge.cs new file mode 100644 index 0000000..46a6fb8 --- /dev/null +++ b/Mobile/Runtime/IBridge.cs @@ -0,0 +1,15 @@ +using System; + +namespace TapSDK.Core +{ + public interface IBridge + { + void Register(string serviceClzName, string serviceImplName); + + void Call(Command command); + + void Call(Command command, Action action); + string CallWithReturnValue(Command command, Action action); + + } +} \ No newline at end of file diff --git a/Mobile/Runtime/IBridge.cs.meta b/Mobile/Runtime/IBridge.cs.meta new file mode 100644 index 0000000..582cd19 --- /dev/null +++ b/Mobile/Runtime/IBridge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 756215630f6ce4667b60a1deaff8543a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/IOSNativeWrapper.cs b/Mobile/Runtime/IOSNativeWrapper.cs new file mode 100644 index 0000000..56464b0 --- /dev/null +++ b/Mobile/Runtime/IOSNativeWrapper.cs @@ -0,0 +1,273 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; +using System.Collections; +using TapSDK.Core.Internal; +using TapSDK.Core; + +namespace TapSDK.Core.Mobile { + public class IOSNativeWrapper + { + +#if UNITY_IOS + // 导入 C 函数 + [DllImport("__Internal")] + private static extern void _TapTapSDKInitWithCoreAndOtherOptions( + string initOptionsJson, + string otherOptionsJson + ); + + [DllImport("__Internal")] + private static extern void _TapTapSDKInit( + string initOptionsJson + ); + + [DllImport("__Internal")] + private static extern void _TapTapEventSetUserID(string userID); + + [DllImport("__Internal")] + private static extern void _TapTapEventSetUserIDWithProperties(string userID, string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventClearUser(); + + [DllImport("__Internal")] + private static extern string _TapTapEventGetDeviceId(); + + [DllImport("__Internal")] + private static extern void _TapTapEventLogEvent(string name, string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventLogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventDeviceInitialize(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventDeviceUpdate(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventDeviceAdd(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventUserInitialize(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventUserUpdate(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventUserAdd(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventAddCommonProperty(string key, string value); + + [DllImport("__Internal")] + private static extern void _TapTapEventAddCommon(string propertiesJson); + + [DllImport("__Internal")] + private static extern void _TapTapEventClearCommonProperty(string key); + + [DllImport("__Internal")] + private static extern void _TapTapEventClearCommonProperties(string[] keys, int count); + + [DllImport("__Internal")] + private static extern void _TapTapEventClearAllCommonProperties(); + + + // 定义一个委托类型,匹配 Objective-C 中的 block 参数 + public delegate string DynamicPropertiesCalculatorDelegate(); + + // 注意:这个方法的封装比较特殊,因为它需要一个返回 NSDictionary 的回调。 + [DllImport("__Internal")] + private static extern void _TapTapEventRegisterDynamicProperties(DynamicPropertiesCalculatorDelegate callback); + + + // 插入 UA + [DllImport("__Internal")] + private static extern void _TapTapSDKCoreAppendUA(string platform, string version); + + [DllImport("__Internal")] + private static extern void _TapTapSDKCoreSetSDKArtifact(string artifact); + + [DllImport("__Internal")] + private static extern void _TapTapSDKCoreSwitchToRND(); + + // 提供给 Unity 调用的 C# 方法 + public static void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions) + { + // 将其他选项转换为JSON字符串 + string otherOptionsJson = ConvertOtherOptionsToJson(otherOptions); + // 调用C方法 + _TapTapSDKInitWithCoreAndOtherOptions( + JsonUtility.ToJson(coreOption), + otherOptionsJson + ); + } + + // 提供给 Unity 调用的 C# 方法 + public static void Init(TapTapSdkOptions coreOption) + { + // 调用C方法 + _TapTapSDKInit( + JsonUtility.ToJson(coreOption) + ); + } + + public static void SetUserID(string userID) + { + _TapTapEventSetUserID(userID); + } + + public static void SetUserID(string userID, string properties) + { + _TapTapEventSetUserIDWithProperties(userID, properties); + } + + public static void ClearUser() + { + _TapTapEventClearUser(); + } + + public static string GetDeviceId() + { + return _TapTapEventGetDeviceId(); + } + + public static void LogEvent(string name, string properties) + { + _TapTapEventLogEvent(name, properties); + } + + public static void LogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties) + { + _TapTapEventLogChargeEvent(orderID, productName, amount, currencyType, paymentMethod, properties); + } + + public static void DeviceInitialize(string properties) + { + _TapTapEventDeviceInitialize(properties); + } + + public static void DeviceUpdate(string properties) + { + _TapTapEventDeviceUpdate(properties); + } + + public static void DeviceAdd(string properties) + { + _TapTapEventDeviceAdd(properties); + } + + public static void UserInitialize(string properties) + { + _TapTapEventUserInitialize(properties); + } + + public static void UserUpdate(string properties) + { + _TapTapEventUserUpdate(properties); + } + + public static void UserAdd(string properties) + { + _TapTapEventUserAdd(properties); + } + + public static void AddCommonProperty(string key, string value) + { + _TapTapEventAddCommonProperty(key, value); + } + + public static void AddCommon(string properties) + { + _TapTapEventAddCommon(properties); + } + + public static void ClearCommonProperty(string key) + { + _TapTapEventClearCommonProperty(key); + } + + public static void ClearCommonProperties(string[] keys) + { + _TapTapEventClearCommonProperties(keys, keys.Length); + } + + public static void ClearAllCommonProperties() + { + _TapTapEventClearAllCommonProperties(); + } + + public static void SetRND(){ + _TapTapSDKCoreSwitchToRND(); + } + // 定义一个 Func 委托,用于从 Unity 使用者那里获取动态属性 + private static Func dynamicPropertiesCallback; + public static void RegisterDynamicProperties(Func callback) + { + dynamicPropertiesCallback = callback; + _TapTapEventRegisterDynamicProperties(DynamicPropertiesCalculator); + } + + // Unity 端的回调方法,返回一个 JSON 字符串 + [AOT.MonoPInvokeCallback(typeof(DynamicPropertiesCalculatorDelegate))] + private static string DynamicPropertiesCalculator() + { + if (dynamicPropertiesCallback != null) + { + string properties = dynamicPropertiesCallback(); + return properties; + } + return null; + } + + private static string ConvertOtherOptionsToJson(TapTapSdkBaseOptions[] otherOptions) + { + if (otherOptions == null || otherOptions.Length == 0) + { + return "[]"; // 如果没有其他选项,则返回空数组的JSON表示 + } + + // 创建一个数组来存储每个选项的JSON字符串 + string[] jsonOptions = new string[otherOptions.Length]; + + for (int i = 0; i < otherOptions.Length; i++) + { + // 获取moduleName + string moduleName = otherOptions[i].moduleName; + + // 使用JsonUtility将每个选项对象转换为JSON字符串 + string optionJson = JsonUtility.ToJson(otherOptions[i]); + + // 将moduleName添加到JSON字符串中 + optionJson = AddModuleNameToJson(optionJson, moduleName); + + jsonOptions[i] = optionJson; + } + + // 将所有JSON字符串连接成一个JSON数组 + string jsonArray = "[" + string.Join(",", jsonOptions) + "]"; + + return jsonArray; + } + + // 辅助方法,用于将moduleName添加到JSON字符串中 + private static string AddModuleNameToJson(string json, string moduleName) + { + // 在JSON字符串的开头添加moduleName字段 + return "{\"moduleName\":\"" + moduleName + "\"," + json.TrimStart('{'); + } + + // 调用此方法来设置 xuaMap + public static void SetPlatformAndVersion(string platform, string version) + { + _TapTapSDKCoreAppendUA(platform, version); + } + + public static void SetSDKArtifact(string artifact) + { + _TapTapSDKCoreSetSDKArtifact(artifact); + } +#endif + } +} diff --git a/Mobile/Runtime/IOSNativeWrapper.cs.meta b/Mobile/Runtime/IOSNativeWrapper.cs.meta new file mode 100644 index 0000000..aec7a39 --- /dev/null +++ b/Mobile/Runtime/IOSNativeWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a625254200cf4a85a707809c2ad07e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/Result.cs b/Mobile/Runtime/Result.cs new file mode 100644 index 0000000..a8c640b --- /dev/null +++ b/Mobile/Runtime/Result.cs @@ -0,0 +1,37 @@ +using System; +using UnityEngine; + +namespace TapSDK.Core +{ + [Serializable] + public class Result + { + public static int RESULT_SUCCESS = 0; + + public static int RESULT_ERROR = -1; + + [SerializeField] public int code; + + [SerializeField] public string message; + + [SerializeField] public string content; + + [SerializeField] public string callbackId; + + [SerializeField] public bool onceTime; + + public Result() + { + } + + public Result(string json) + { + JsonUtility.FromJsonOverwrite(json, this); + } + + public string ToJSON() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/Result.cs.meta b/Mobile/Runtime/Result.cs.meta new file mode 100644 index 0000000..54a9c47 --- /dev/null +++ b/Mobile/Runtime/Result.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bed21977dc864ab3873055bfef79966 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/TapCoreMobile.cs b/Mobile/Runtime/TapCoreMobile.cs new file mode 100644 index 0000000..dc7eed6 --- /dev/null +++ b/Mobile/Runtime/TapCoreMobile.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Runtime.InteropServices; +using TapSDK.Core.Internal; +using TapSDK.Core.Internal.Utils; +using System.Collections.Generic; +using UnityEngine; +using Newtonsoft.Json; + +namespace TapSDK.Core.Mobile +{ + public class TapCoreMobile : ITapCorePlatform + { + private EngineBridge Bridge = EngineBridge.GetInstance(); + + public TapCoreMobile() + { + Debug.Log("TapCoreMobile constructor"); + TapLoom.Initialize(); + EngineBridgeInitializer.Initialize(); + } + + public void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions) + { + Debug.Log("TapCoreMobile SDK inited"); + SetPlatformAndVersion(TapTapSDK.SDKPlatform, TapTapSDK.Version); + string coreOptionsJson = JsonUtility.ToJson(coreOption); + string[] otherOptionsJson = otherOptions.Select(option => JsonConvert.SerializeObject(option)).ToArray(); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("init") + .Args("coreOption", coreOptionsJson) + .Args("otherOptions", otherOptionsJson) + .CommandBuilder()); + } + + private void SetPlatformAndVersion(string platform, string version) + { + Debug.Log("TapCoreMobile SetPlatformAndVersion called with platform: " + platform + " and version: " + version); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("setPlatformAndVersion") + .Args("platform", TapTapSDK.SDKPlatform) + .Args("version", TapTapSDK.Version) + .CommandBuilder()); + SetSDKArtifact("Unity"); + } + + private void SetSDKArtifact(string value) + { + Debug.Log("TapCoreMobile SetSDKArtifact called with value: " + value); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("setSDKArtifact") + .Args("artifact", "Unity") + .CommandBuilder()); + } + + public void Init(TapTapSdkOptions coreOption) + { + Init(coreOption, new TapTapSdkBaseOptions[0]); + } + + public void UpdateLanguage(TapTapLanguageType language) + { + Debug.Log("TapCoreMobile UpdateLanguage language: " + language); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("updateLanguage") + .Args("language", (int)language) + .CommandBuilder()); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/TapCoreMobile.cs.meta b/Mobile/Runtime/TapCoreMobile.cs.meta new file mode 100644 index 0000000..da4905a --- /dev/null +++ b/Mobile/Runtime/TapCoreMobile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 058fcddd11e6f483c92c09963365f0ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/TapEventMobile.cs b/Mobile/Runtime/TapEventMobile.cs new file mode 100644 index 0000000..be8c658 --- /dev/null +++ b/Mobile/Runtime/TapEventMobile.cs @@ -0,0 +1,265 @@ +using System; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using TapSDK.Core.Internal; +using System.Collections.Generic; +using UnityEngine; + +namespace TapSDK.Core.Mobile +{ + public class TapEventMobile : ITapEventPlatform + { + private EngineBridge Bridge = EngineBridge.GetInstance(); + + public TapEventMobile() + { + Debug.Log("TapEventMobile constructor"); + EngineBridgeInitializer.Initialize(); + } + + public void SetUserID(string userID) + { + Debug.Log("TapEventMobile SetUserID = " + userID); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("setUserID") + .Args("userID", userID) + .CommandBuilder()); + } + + public void SetUserID(string userID, string properties) + { + Debug.Log("TapEventMobile SetUserID" + userID + properties); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("setUserID") + .Args("userID", userID) + .Args("properties", properties) + .CommandBuilder()); + } + + public void ClearUser() + { + Debug.Log("TapEventMobile ClearUser"); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearUser") + .CommandBuilder()); + } + + public string GetDeviceId() + { + string deviceId = Bridge.CallWithReturnValue(EngineBridgeInitializer.GetBridgeServer() + .Method("getDeviceId") + .CommandBuilder()); + Debug.Log("TapEventMobile GetDeviceId = " + deviceId); + return deviceId; + } + + public void LogEvent(string name, string properties) + { + Debug.Log("TapEventMobile LogEvent" + name + properties); + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("logEvent") + .Args("name", name) + .Args("properties", properties) + .CommandBuilder()); + } + + public void DeviceInitialize(string properties) + { + Debug.Log("TapEventMobile DeviceInitialize" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceInitialize") + .Args("deviceInitialize", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceInitialize") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void DeviceUpdate(string properties) + { + Debug.Log("TapEventMobile DeviceUpdate" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceUpdate") + .Args("deviceUpdate", properties) + .CommandBuilder()); + IOSNativeWrapper.DeviceUpdate(properties); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceUpdate") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void DeviceAdd(string properties) + { + Debug.Log("TapEventMobile DeviceAdd" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceAdd") + .Args("deviceAdd", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("deviceAdd") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void UserInitialize(string properties) + { + Debug.Log("TapEventMobile UserInitialize" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userInitialize") + .Args("userInitialize", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userInitialize") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void UserUpdate(string properties) + { + Debug.Log("TapEventMobile UserUpdate" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userUpdate") + .Args("userUpdate", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userUpdate") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void UserAdd(string properties) + { + Debug.Log("TapEventMobile UserAdd" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userAdd") + .Args("userAdd", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("userAdd") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void AddCommonProperty(string key, string value) + { + Debug.Log("TapEventMobile AddCommonProperty" + key + value); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("addCommonProperty") + .Args("addCommonProperty", key) + .Args("value", value) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("addCommonProperty") + .Args("key", key) + .Args("value", value) + .CommandBuilder()); +#endif + } + + public void AddCommon(string properties) + { + Debug.Log("TapEventMobile AddCommon" + properties); +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("addCommon") + .Args("addCommon", properties) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("addCommonProperties") + .Args("properties", properties) + .CommandBuilder()); +#endif + } + + public void ClearCommonProperty(string key) + { + Debug.Log("TapEventMobile ClearCommonProperty"); + +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearCommonProperty") + .Args("clearCommonProperty", key) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearCommonProperty") + .Args("key", key) + .CommandBuilder()); +#endif + } + + public void ClearCommonProperties(string[] keys) + { + Debug.Log("TapEventMobile ClearCommonProperties"); + +#if UNITY_IOS + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearCommonProperties") + .Args("clearCommonProperties", keys) + .CommandBuilder()); +#else + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearCommonProperties") + .Args("keys", keys) + .CommandBuilder()); +#endif + } + + public void ClearAllCommonProperties() + { + Debug.Log("TapEventMobile ClearAllCommonProperties"); + + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("clearAllCommonProperties") + .CommandBuilder()); + } + + public void LogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties) + { + Debug.Log("TapEventMobile LogChargeEvent" + orderID); + + Bridge.CallHandler(EngineBridgeInitializer.GetBridgeServer() + .Method("logPurchasedEvent") + .Args("orderID", orderID) + .Args("productName", productName) + .Args("amount", amount) + .Args("currencyType", currencyType) + .Args("paymentMethod", paymentMethod) + .Args("properties", properties) + .CommandBuilder()); + } + + public void RegisterDynamicProperties(Func callback) + { + Debug.Log("RegisterDynamicProperties called" + callback); +#if UNITY_IOS + IOSNativeWrapper.RegisterDynamicProperties(callback); +#else + AndroidNativeWrapper.RegisterDynamicProperties(callback); +#endif + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/TapEventMobile.cs.meta b/Mobile/Runtime/TapEventMobile.cs.meta new file mode 100644 index 0000000..6314a14 --- /dev/null +++ b/Mobile/Runtime/TapEventMobile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4afff3e4af4824f50b0b4a91897cc20a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef b/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef new file mode 100644 index 0000000..9daf7a1 --- /dev/null +++ b/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef @@ -0,0 +1,18 @@ +{ + "name": "TapSDK.Core.Mobile.Runtime", + "references": [ + "GUID:7d5ef2062f3704e1ab74aac0e4d5a1a7" + ], + "includePlatforms": [ + "Android", + "iOS" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef.meta b/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef.meta new file mode 100644 index 0000000..f100b08 --- /dev/null +++ b/Mobile/Runtime/TapSDK.Core.Mobile.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 10560023d8780423cb943c7a324b69f2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Mobile/Runtime/TapUUID.cs b/Mobile/Runtime/TapUUID.cs new file mode 100644 index 0000000..387d95d --- /dev/null +++ b/Mobile/Runtime/TapUUID.cs @@ -0,0 +1,9 @@ +namespace TapSDK.Core +{ + public class TapUUID + { + public static string UUID(){ + return System.Guid.NewGuid().ToString(); + } + } +} \ No newline at end of file diff --git a/Mobile/Runtime/TapUUID.cs.meta b/Mobile/Runtime/TapUUID.cs.meta new file mode 100644 index 0000000..aa3ae38 --- /dev/null +++ b/Mobile/Runtime/TapUUID.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e61d5874559a448db9aa22254d05fe7a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources.meta b/Resources.meta new file mode 100644 index 0000000..a9ca71a --- /dev/null +++ b/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 85e6f39839e6d4c69ae3b8d6b4928cdd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/Fonts.meta b/Resources/Fonts.meta new file mode 100644 index 0000000..a927f6b --- /dev/null +++ b/Resources/Fonts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5884ad20cef2e4e5b82f45e69b0de123 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/Fonts/taptap-sdk-bold.ttf b/Resources/Fonts/taptap-sdk-bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e278157fedd233445208d407dcdc8286076f46d1 GIT binary patch literal 9244 zcmaJ{37p(kdDoLhqhm(WXf*ddqq*;U_P*@(+TQiC>-BmM?-hG}B)(!hi4(_x9GHff z#9YRKw17z*5{~erfuulan$XZDsha|Yf+=ZAXiI^Vl7<-YX#2gHS?q+g+TWAjdwSBm z_kZ8<|2{2&2!b#W?$A6x!p^4)582^w>#)E(x z1^*f@9=O-MQjJj_LPxYgKTog(34e$P{LH^xUtVrJ75Ev^sH5ed*mdyS)$c_Ff>F{{ zeX#yodt=CzasPjxo=7kMmU@YL2zJgwIEXm0nz$aOuUgegvjmY3*=t!8W9&g|kc!5j z8jUeFJ9OLZ_G+yM)vC1^qi00K=p_VaJMN1y5f$ns*@Ew=@KniGEETKWNR_YrmGDrt z60}l}&D=h7Y#XV+{O?MH9p_?}&97^pMO&XEDIL!+>{^D=^EwJW$+4V?VVE&mN7DjS zdenZG^^U^s5OA3ixZZ@3`Gtq2zA)il)g;X{-lS-)~E9mEfMwc+D{I?}; z(Vp}Kd5)G-64EnoXwRS??P=7fJ%K(J=Pmr!t$5=|BC`AjwVV1p;U{8%CxFESWmsqv zCI;YWBG-+AP^W`d4`K>I(rHrJ5?ZrZ*JzFlIvuJ|Hm95}w~j_@S)Yi8H3}h(`n)>o zHEEOGwf6Qy<#I|0vz6Z7JNx?De&rkXzjBt9u@=xoZ22;Ek@_OhN%RtHiBVz^W@3j; z3D)qrwS`cF8Q{5DRk_({Z7OuXrH!xvh)8NoxfWz#QLuZI5BcFv8iHC0%E1c$ zLw|PS#EIF{r&IoRr)yYri=3fmup2pc}Pe7i#?)uSt;m|&L*InDTp@r%Cf1xkmcH8W^ znLF>CIX8=5b3Z=$+;fu`6dkME9(;{wk+~uxhBqWs#Dyk;$KlYP0N75PEM`dW?%pk+B}sOJW(s$}eHWdXc>;xX=;4Y8P_0%jFC>#8WsPD|747_H^i1_| z=fs4@Y(*XmZO~P=_4aIYx&*UClBBty=n$gxI`e%WC;jonwoE#vU!9tcZ?EplPS9*Q znn;ePk_o`gOUTr#)Taq6;e`4;;0Ehd8)O1N!(tx<9Abb(um3uyLkoa{bBu17i!7o_!TF3kWk#p()KL_T zNHk)qe}tT+>fa*Eh$QQW(8*7%x;a~ZEU_iYaz=Sogg4XaM3>EN3B;n4g<_45m^PlQ zX7^;q>RbA9`va@J*P`b`8w1_})ioYVCdU(rD6N|s*=mU?<6r1=XH65s=CE=IJUA)m zz>U!?7mGZXiADRJPP0-JEkilqaHM;n-MPls?;UCIr|5E>`b+9Fz;E+_#UH$v3=40X zOaK9hWD}&89svh|rDAy?pSpN);o?P2BX?-a8cEcD=KsQW2`Q)Vb=F}AV&`fntUbCVTj*LaA$X-<)&7^ZP(c!4m-y4955M}UF7r;+- zLj(4p!EPI@zz3?2=`BpJ@ppw806&IlRckGq%fMssM_tgCJb}Qqn`nn29~-cUvZ(Du z4``2~i9|9n7LUh}$P~9tPdzfczHKNJiEK(H(`Z5aB>FgdVD4{nO;*sRAUFimx8^=D za}Na@N|8t|nn;>qErMX$$g`}d*V7-J`2ffl_9qv?pM;4WF#8&w_7z^xnr};!S47wY=TH`WFAyDUzVujUTMLzgvIPrZ37sh$9h3Gu?wJ47S;4HN zold8~TlMOIdtSDs1oWbn<|rUOa7ZyT(&-Q-84NzTEUAKy=g#T%;b5q0%*#cuHvk+M zEs`X~&}e46$-mF(pMLh)>0-IEBa;p(QrMI`xnAo>mz?UL{W84Oz=J(Z8f=U`JY><^ zJRHj!j5=ecO5Xm9zTx#BS+k}l#<^lw=ev4)JJ5JNV|M|7c#o?@ntFjc3o#K9X~-L( z0awzD{^Ig==*C^>uy*gMg$2r}y#z*$1+M>F>T&83V2abk9B~bC9dVF2Lfi_k!m?Ou zNaR(4kiCR8w82W?_7&JF7{@D3#tH!xP)I=dt#aOrK+q|0LD;9qRM-?NupLY^Yfz}> zQ$2`Gj_C#FNwM2pbbmp#S_ZO6U{;wp{qZfg@x_c0Jha7XgP6)ou@tKc0iU;QwMl1A z@lJ{V{1-MG?E>Wl3@rgcFzAk=>Feg^$ingC7Y@O89XWL9!tvube3o|z!b5V1dc-CN zOuBezU7-GSFv|0^IVU5e=k9s&#eqAD#sCvdXJ&J`)C{6EUF9+C=icN6$)kBgmy&xw~KrB_yo74?0Iy4ejXTjTrpe9;Drki*SridD+|%| zDnJ=9Yj^+!a}-R}K~%BI;eJ#CP$5!H^;+4Yh9;F^yN4HbpBtY$Jxm1?P@Qe8}?!gAa&xVZT0 zZIJN*`HL?9iuy;8C!DKTF&dyvz|mn95KM=nv8JTJ8e8kZCIyQRmY62d!1~u<^)>bF z3R;oDeO5|(oBb*t8NA{$sN#Z2Q8G@K$JG_ESZFz`7(090rbFq)iXkcYU7r&TMl++Q zwI_iIP)0#;n^~nlf<)fpO7aFt&+_DhI@)fvhJa;x&9XvK=pmBY2@d8t-*loqyUSPH zt9_#p!?SFp!$vOJi`m>nA|B}<-nvuvG4GunU6N zLk-^&1AYAlb%rn#n*m`4s2dIyFnrC_1?bb;XR76o>!8!}HUiX;ZWE!lsP4IE^rc$X# zl<1HB8My$inMXG#(B0G1+8ob{^p3TVUzkQCnR||I89NvVnCt*6X{jnCiS)cN7&taF z^X{eXv!Cc6C@5(m)!u&3=-Bj3X-~DX7gA+0hC9wY#WZoP^5aL^$aci)zhRagH za0g{fA+UT!e8}2RA*?%%N^c{NkwxThH6HzDQF{p;)Ba>pdzt)FJ)Ru0PdkQUD8(5U zkz|3;MWk9LIl@}OoI^3VYv@l$?A7@?5N+f z>z}kk=(P41XlN%Ig3BrN*V?yU*N#`vreCA>`FrQioSC~9EH;ku1Mu$8Qy&H_T#fw5 z7WfUI7Bu!3NR=CvsYXr{vDDW+Nt*K(N0ymYbZ9F6iaAb-uYtN!H zU=c`3l3Y>-auN+?I75AU^O?09H%~x%i*-uyi#AC1NIZIQe0*wBaX7}>NSRA|(wx9i z$pZ(TMGozA)t;W)y1I*uYOJkZ{r)wB9bHEk7GTN!+5~#^Cp7p+giL9OL|M*fN1bS- z{tfb@mw%GT7>ta$-2!(ZB?e&P^(P=d@LlS&;Nz^s5V4841=f$Vbl7&P)*$xsPy`g|uFbQ|>?a{EP0?#;8Kzh?AE&dT7)xUlc_?#N6a?w<%FWm40j%Qmn)@9yf( z3t=WzDqUBp=Fvfx;x`)gGkTzF;FYO!ZEdw(nM~N!V|G-1^&gOrm-p4&+PZWqy*aFU*?_?x zk4@$CMfst~piAaCGYNymJl z#cr`$Ln~UXG4@!)D@33f0f7gL1KI#t7V72r?R}+j-zu}VA8id~v->(aJES1L>aMO( zSY74yS=uhY;q`c`E|+~w3dqN7R$J1n$TPqi;Ex9 zA|HQ(|9E8~DkG~13E$D**B@Sf2L0#XlB~1elew-()lK9Q$w9YUk@9BeKuR*3Raq8C z-V+F^Ya`KU#a(k5puR2|jY9S{v%%vvx&k_-T_p7eKGh@27AK{&Nfhr<#iHW>&3Htx zv2*JomGze|Pffi%0ikEpSNx9pXX+Lr0vW48Vkj9j9yWMHkL}Ftk33-nhW>yi=4+ipk_Q z7+B5lK{%*R)0XwaCp$YUy*oof+N{pCbuLEaq!7M#Fkh)0>F(~Ip?W9kuZyGdx4r_T zB0Gy9g6y5p^rv)28hy8r&rPS2v1l?mozIotdlwqRG!58iV)^&rpB^UMkS8t?HQ=I~ zKq7YnPN33_$Pimp$QOe+ffH#ukQEH2m2jz-g!Df+UTZJ{{LYVI&4IXGT?epXcn`us z{K^3GneQu3=jIN|%=!Ig&cJEsXa9LoD-D`t2(}foXP~=LuV&}NVlcL!9sUed80l`g*~k)!ob2s=5B=Pd>Uzu-XEYunfC6&>`gy9Q>-Uu*XV*9A9Pt=2f` zulAjU4ez~A5jW8S87NK%jTRUN|Hw~a;q)cL*Qt38BKD^ z;BbQ(a@r;xPG`a4u#cce5t3c5ky2^E6y+^U+ zsEm{Igo5j1v1nWmUv8A{uCD#Le3;{%TykG;yMM&J+P5XJNxPg%rN_b%Prn+AZB8U& zedh*XmcxDh*T&*@sc5nL1Dgx^wsa=FH6Bytj8N_GJK5J)!E_wBNE_jVc>6)3LbL;x z{a|mdSehoo!`jjOYYm0=bKmR!C^g3vyeqy zHB#38s$+QFh7Ie6J5X8%Sq8lp%Rtt;yBN1pW5QGOr2ecoH5m3zr3J~fymx^ptO41=uJBm&wXpBU{ z{a%j^J%rraPwQG;@9{(ukn${4#2{BG6sMDkXmsB&t4gbPhQO0-v~@rtfPU?NwAYa^ zH}}8;h&XqSdeoPmN_*?=WKW>eCHF$Sn%-5bwX?gnO=|yAoKJ#@b#NSQDuSyvc+HA6 z+-WoAB~#8D9L+k_9#>!}i~^I}c4-gXaR+?1z&<4dar-}l&ahxH1K?S@8<=37zzXsX z(Yw8Zts=NxoFB$fT9a+YU^K96u&R~D#}i1AHXL)~^G@r72pVv1PjovX>nzH@p9-0b zK0PgtA7d@L51^R#gJzl5Vv{Jw@>ww;vnDDYsVa8)8^}=qAfjJ*;io^H`^7IVTp%y| zw*@s3ZgP|-h7G%9?aJ!f{cdlLg!m3q6n&qQq4!&{8c^R^sd@MlfhK9NZ zDnlb9LzRIph-E;e31aA}SNG3LU2hNu9Y%!s;VU!0x^ji6|5f-}i#-d8d*Og*8vj=S z1_vM1tBp4`>aA0_d8gUJk3l;PB?2V|B?Ki7B><%dO0-q?!gY14U2a{gaK+CRT5bG% z63PG+{Con60wo9q_v3aBN(xFF6bF<{VBA<}Ws zBf_0#orL=CR-J-+cdM?0`mL=xP1uM)tFDLsu~wajFPabT-EnaL!Gi}N0N4Qu!u?PV z5(g%h4jEdj3y aKyLG9IIn9gU_)~OFh>x~28#dZ>3;#$K#D5> literal 0 HcmV?d00001 diff --git a/Resources/Fonts/taptap-sdk-bold.ttf.meta b/Resources/Fonts/taptap-sdk-bold.ttf.meta new file mode 100644 index 0000000..bb0282e --- /dev/null +++ b/Resources/Fonts/taptap-sdk-bold.ttf.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: 5b92beb2e4ac04c1681719225dc6d5fc +TrueTypeFontImporter: + externalObjects: {} + serializedVersion: 4 + fontSize: 16 + forceTextureCase: -2 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontName: icomoon + fontNames: + - taptap-sdk-bold + fallbackFontReferences: + - {fileID: 12800000, guid: 922f25809659d41b4b23147484bd150d, type: 3} + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 1 + useLegacyBoundsCalculation: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/Fonts/taptap-sdk.ttf b/Resources/Fonts/taptap-sdk.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f54b82f71e3e4b1054444755c6daec8af0378070 GIT binary patch literal 9972 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 literal 0 HcmV?d00001 diff --git a/Resources/TapSDKCommonTapIcon.png.meta b/Resources/TapSDKCommonTapIcon.png.meta new file mode 100644 index 0000000..bb4ba22 --- /dev/null +++ b/Resources/TapSDKCommonTapIcon.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: def1d4bba5fa6450b92e6a98dd48cb6e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/TapSDKCommonToastBg.png b/Resources/TapSDKCommonToastBg.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb47805251bf3d92113e36c87dec8b7ab1b6b47 GIT binary patch literal 1355 zcmXYwdt6dy7{?D~X*rvjw;7tBbXi)MU8XeBSrV8<-mOGd)4~g(A2|w&IntV!rq0pD z605DU4FuCN5U-mnO+YE5W`t!1iUzhNVu?1+aeti4`+I-S_xZig`J9q)0?uZ+%W?n! zY(nuNkpN&ZW`6g!T57&83s1sAj|}`F8UR?Yf-VGbnd5905wu9$E%1J%#s?C@ih9R%2s$w|MrC&mxSHw)b zF`LO`YG?}3Ybp5on!r5b1+|Y+ihv0BvG}+c1>H-v-}sP42!JD9^eO^*=o{4^qVaKV zFN+iTXwEG5|1A&li6}HJzWA+fro{u1uGZsSqIY``e44I zXHa?lFn_wMvTfM2cVeBF*QdRZzcXf>yj6Cbw!zWT0Yr0{mmrbFi%qhO0Sj3u@jXp9 zxCqkr92GxG#*{pCzvd&!L_tj8x?Nnq?5`wSxdYa(8WLC$xh}2=lDSB6?m|uN`qEr} zER#4rdy?J{=NPjoZ@w_dhG+UCF~Yt~R7mxPX&%oejUA;dK_yI@2>0c2AG^aCL2wdH zt=Cf*nCWD)*ZB_zd=`r0?6E%HV-_G(lwl)Q>6MKGTGPTQD#wkp8^|>NmRq2V%e8|n zn|Gku6X_>T%&nx$W$6@(CnO#LpX;(QjuO*o7D-gJ0+RN4mJ0>-^|*CHdqe`yOgx8M zh(Z!7_qUPK12yXTA=CJH>Vg^@-OsP9n|eQWtgZ;5iX`Y9pFMpFH_X^RU2(6rrl#h8 zYo04Jv1rJj*)}u~FQndVXlQ_^KolUQHr&z1!B*tdnE*Sv+R2dJGH1{!g;5dq%b+kK zq*AF&$;m&3akaN`A7tz7VDD}Y4tO?U7MGtdwg(21I_Q(6%T-l)m@$amT`Z!Z z{%kgxzD_?`J-vE2GCR`0SdGc&amTA=GTHB<3jKq!)o`!?)$RC+-rl{@@*3y9GXpz< zxBYvtXXMe$3`5K`QpGv%kHh0BFAYb5J8GV!Hbn}RWo7;;zMQlxQ*8}V`Tl)f^PFz; z$7vsW@nWAYn<*d8qEPm&g6ZwaUEwG)IVCaCgD()&HZ?WWChI985u3C+}P8B78w53Znz3q?JE}BRDk#Osi-}5FbsC6io5F*%hq~JgEw@>B( literal 0 HcmV?d00001 diff --git a/Resources/TapSDKCommonToastBg.png.meta b/Resources/TapSDKCommonToastBg.png.meta new file mode 100644 index 0000000..6fd0ddc --- /dev/null +++ b/Resources/TapSDKCommonToastBg.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 121fadaa5f515439bb5fa46c7f9d2c2c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 36, y: 10, z: 36, w: 10} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/TapSDKConstantUIRoot.prefab b/Resources/TapSDKConstantUIRoot.prefab new file mode 100644 index 0000000..e49f57a --- /dev/null +++ b/Resources/TapSDKConstantUIRoot.prefab @@ -0,0 +1,100 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4261950774861981618 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5350339549409990581} + - component: {fileID: 2204181717241611008} + - component: {fileID: 4600610754894736554} + - component: {fileID: 50905930539700672} + m_Layer: 5 + m_Name: TapSDKConstantUIRoot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5350339549409990581 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!223 &2204181717241611008 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 990 + m_TargetDisplay: 0 +--- !u!114 &4600610754894736554 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 1920, y: 1080} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0.5 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!114 &50905930539700672 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 diff --git a/Resources/TapSDKConstantUIRoot.prefab.meta b/Resources/TapSDKConstantUIRoot.prefab.meta new file mode 100644 index 0000000..d15c871 --- /dev/null +++ b/Resources/TapSDKConstantUIRoot.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c1739bdd7bfc64f5e81fcb8fcda40ea8 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/TapSDKUIRoot.prefab b/Resources/TapSDKUIRoot.prefab new file mode 100644 index 0000000..4b2ac9e --- /dev/null +++ b/Resources/TapSDKUIRoot.prefab @@ -0,0 +1,100 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4261950774861981618 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5350339549409990581} + - component: {fileID: 2204181717241611008} + - component: {fileID: 4600610754894736554} + - component: {fileID: 739291991273991942} + m_Layer: 5 + m_Name: TapSDKUIRoot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5350339549409990581 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!223 &2204181717241611008 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 32667 + m_TargetDisplay: 0 +--- !u!114 &4600610754894736554 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 1 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 1080, y: 1920} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0.5 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!114 &739291991273991942 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4261950774861981618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 39341cb1058fe4bdebd02bc59f5c08e1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 diff --git a/Resources/TapSDKUIRoot.prefab.meta b/Resources/TapSDKUIRoot.prefab.meta new file mode 100644 index 0000000..7febaae --- /dev/null +++ b/Resources/TapSDKUIRoot.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5e63b41df783540f79e94ed80e187f4e +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/TapTapBtn_White.png b/Resources/TapTapBtn_White.png new file mode 100644 index 0000000000000000000000000000000000000000..91f1fed77845010126373b95d48ddd8cd38ce9e8 GIT binary patch literal 1042 zcmV+t1nv8YP)K($>!O*c?+0j+igtz7|j1JVUFvVsmi7~@x% z7hVLU=g`5F77%2nY3=zXH#3d{Cd%=C&D?QpOeZHNnd#~2wN|T@a~vm+l4!Tv2@|>J z@LBiqS+8QZ>bhyjeQBOQOJKMkyT*qz>KXeb<3QN6SuV%AZ-$d>?&+`(q zv$GilEpvT+UBIDLh7e^pl}hbd7|zen^C%w50ryQrZd_bk6oPDc*k8B1s?}-G#wo@8CTNjblF5?AT%jU@pya%yAHB*$PA72 zLY~O#@$~exXd*H;-$=Pxfktl3qifyB_ z$DvEMGfhMygIcb}>yX_>qfy|6m?k2T;q}L8MKWvdD!wk6h(xJ^tS!4p9QAXK{<+HsFI5TDR3eYLD0BZ*BYka%5JOGDg~=?iAczf zkB>Vw95VJ0f!lYNmX@kGUeQ89BH^2vnc;@Fz4^>qXIdawXL8F#Bs8quxDXf0tKaU% z(Quo2KNj)7$WB||e zY<_;e(`Rcon{N=lRWvD|1ygQ4`ra+T6MtSO9jRS25$VC8p(SW@b93ykJwjH#vZg~b z@^Qbg(XG#i;}Uw4nJa0`PlBg3pY55jgR3=A9lx&I`x0{JGME{-7;jBgJxG6H!`3jf#Vc?1Aic)*9$ X>L(10*;k)RffRYV`njxgN@xNA%Y7g_ literal 0 HcmV?d00001 diff --git a/Resources/TapTapBtn_White_2.png.meta b/Resources/TapTapBtn_White_2.png.meta new file mode 100644 index 0000000..f617d85 --- /dev/null +++ b/Resources/TapTapBtn_White_2.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: eac19805067354a63986e9c27b7e622f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/ToastBackground.png b/Resources/ToastBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..98d4e22eebb7771b4365517fe44a20cbc2abe809 GIT binary patch literal 13759 zcmZ8|WmFtZ(C!j~dvJGmcXxLP1a}hL-JK9Ti@UqCKyWAMBEj8lahH$x+K+qANY9)z z-96P+Pd!!L9j&G!i-Jgi2mk<3#v;=kGc~iB#Ia_v5=U(B`93|L#aKzG3WA z-y`(UqKV$9>w}ba(RR7-N=I48=R)ZJggIh)C&_ryva?04(Y`Ozwe@Nfq?I;v45P3- zdJnf=Fm^xr+kgtWR&$}k`mi50(y)bVAJVT28gTBjx`q=GF1GnxkDS{iMJm-wvRLwOephs+6}P)1y7EQsewig zcB^`3vInq(#LE!LQqoSKPh>0v>An{&;G!q{C*gI)(3C+&;;;f}+tAJRX!w5ByX{vQ zQ|z(H-{%4m{38C`HWwEm^35~`$30&U15qfp*cKY<#VQImW~fq{vsH;h_wwl7*DM)2hq5CHg;=AEX zv%fQyweVR~dg=R1`-T>Y>)xu4wSma0rT|=Xo#x|%;IAon{J&zRXeDDBf05jk?Y9$Z zn_IMOWijRudrYJ04pYB>Q7ObMNF1S0b#{?%s(nKmQz}G@LyQ|G)NWt)cL{(%7>}?n zh1Y)yRu!MwDi?7o0LKz$qx=Nfl*1AJ#2diOQ3OV6?(H}w;#RaWh`xvv`GUgV`}Oe| zABl%tBKRJC7nWm6@u8}|1bmj$%%kLDN=DgxHWi|uwjtMcr@e9DdixzSLMT2+h|EjW z-E|-g;b+W@==;}rGx<8DXpCZKN`FUE)W<3M0x+?7I&-)n&$f9`y2H>;h}N>b;3xjGDHS`+vN$T`f%o=s3L&@pjGlU&b1r-k78M zU@Jg;=Ux%q<1?N~T+OB&r5TPJXP8TCD?4h=vE5l0uN}9Hg7;fljQ*r8m#urMioV|D z+3S$_9OgsWe}(uuZv4hse!qxT_51XtBk*6ZQItDUsh|v}UnzIjFUD9X7>hiOiz?Dj zK_QiZuZQ#ZmCusLZ%yqGZBKau?sixBp6iw4x5)MlwS>oa0MVa1Q5ol+-dx&|H#tqb z6C~)TP$(6eC-bf|F;}|QTn>p+xIs)f3>nL611$?EIx@IaU8CMysZZE|F}NZqg+js1 zX_4-Ytg_G02nYY>{f*Y%gEu};&rtyp+OEdcu(38tXst6lI}$tD&a92pO7<^F`m7r|J90Dh1}+J9`xG_-*hTW#t+Q@VbS*qw_8=h|&_)J+_zLHrM(y z7n26V`qS)BeVN?9TtLm#aY2g48EpHEDH_7)Bp4J;G(k{&FM#|BBxWYQ_nu@2u%ZV^ zojuDTiDDR8^^NawrrsNXivPccr~LnKyBUGiacBJ-Xrm?7g%i+T0zezN4An+ORiR5M zY#~44xX`#$V_Csj=0p?ZuVB2H1g`vY`mzQoXknz9G7uYd*vYGYeFt**?;x2%nby#K zs#W@@AbvT{+=cSX^RZOMa@KSaF!ftX#69;-HCw-y9gnbm^u89hdDnr!qLpnwr8PJ#gSQ;Jls@|0i%2G} z)J_Bw6HF^xRez9Mg(Ma0v|0u_9lDmBo7SjhxE}d@BUNB_OTW`0?fffad8neUGY6&v zkk8eY>$_HAn<*H4*GMJw^yz6pwIJRmx3^EIDqFs>3e>9ik7$EdO{rltpfrxXblwz% ze)Jta0}vM>;q6S6qdW}md|rc`=lI|0(K)L}$DjB5LWjtB5~BEa1#Ydjxd!g-Ph?I} zB1fp8iM}|YMGsjS#y+r&T1;K|f!)5-KR3C4wOpL9NyG2EZT@G}5BhSC_v0;W&Yigw ziiyPWNGT-jqGLdC@^q%lZB;Lqnp1_M6>jl9`%KV&S(j@O&ZZ9jl(hW$&!=&dcKrJJ zQ%PzEdzg#zD2~$nP-a|BVMrRW_vvi@xVU=eYrA=C;VSvdMS^BM$t4*5%T zoIBU8SSUlQthj82TcZgneG$p)x$@lbyPlYoL3o>-5U)}qC79(`4}YzPDk+(E=c>ZW zYZMa1GT_;~+PQI{2iZaY+|g3`w`#a!k-s5gdbcMlL84fmiO?M@T6(>8kmGx9j>v_~ zF>3UMXbz_fGPRRtdS&>KQ=FV&Qi0^-*8(4Q%lt*828T!kDfBPh8vZ+dcUwHx?An4H z!P;X}lm98E6=@7$C_r&#(V(NYbLIEGadI42GSjwc_RkZGfhU#-#hPSxS9;o#Lt!21 zXA(*$>A!E3Nu|~+f?GimsZsAnJhMMKLPj*+iy?&E?Q2}8mkybz5wC2R8I+3A!mmmMKZpP$7uhZi&jhV<2blRAnoQ- zulz?pWxnBzS;S9GbX^wPUZ=sD!(>=svH6NU*6@_(lqMwN!eNozA@1EFHd!Hu$g_rs z;(D_o?4LfqrFa$r-Jn+k^&3$j#q4c6)q0EHz5EZ|ig}psZ(q9ZKA1G_O>e4l!vLs| z2Meo95NKHnuWp8Lec)Fl5wPHFoWTwb*;>Q3_`~Q^(bp25el6;-C=;iWQ9{*H>kQ%t8r6W`evwVCbw)x5aPCocAQ6DL-U7<{= zw5tX<38eU}&`M3>mP_gq_>--w)+)fydvvvELPMV@?S{qs6csJ^*WGm#oicK)4lE1c z_dCVk0Z`ikeYfE{?{~LSutvER>1g;XTjfn?1HV8q@KU5$Wbv7JB;Za(W4tE2UO?x% zP^!)w;~T(X6_iLtnf>Kt{kF!ly2h~MPTv0oDb>wl$nl6RdIJp%D4F>d8bQ1Pd3wp6 z7cQ>R6&KS&2Tb3vT?PRy_P4$wZb)Y*{_6`3)USJxNF+`S290QkL*r*jHVFtw=+)q0Ql&p|c^`Gi#H}N`Wh9MvtKZY}Cu#VBsJhTq$MR>{k{wd-mKYMMQYG&{u_H19Ara>_v;1)o=%)Om^_Rrl2gmm5;A$k|x!?LJ6uHUjbJlhhUb)eGZw!kC6 z`rv-6?$9`*eiXf#mRy_2&Ma2--iY~5dMc>^-Sq;9QVouQh z6{L|8muvc#ty(i3pG{m80>*=LG0q^`kiIRA46Bjn+A)r0?Subfo+OWSm3>cApNn3& zj!NCyl42SbGQa1Q4WpUwFH$1*D0qj@Mi%0tYlv)qRZy&T{qtyf2i2Qi5jO=uNOB=2 z)IOj{o+XapOhz$M(G~LYxUIteBx=D}oIF{D_)+eDZ%$GTxJr)wCA5Ud#)O#Y z59H#ya%Z(t<%Q9$ZX$htdMl^0q!F~8E1UVE|A|9!{zHCV2r(ySFCwfx&~BUm}mR>i6u;m43?%c|O%3Y7vOsb~9mzp?&6eE{xtQ_+Uqb8@61 z4RSIW8I`ErC3|Db{6k6Tl0xzUg)}q!H*#yI-uUXp+n;(q(J=q@wmP56`xN~Rt>PIa zUu1&S-qP`^aTeju7;Zff z%y-S9;VuuP^MjXYv+t=UkpohVA<*#wiLl3*LOFUrY6?* z^CsT|!HsnH3IT6S`ymUBAo&tPC5XOq2O^so6d7kD6=;XL^^+4iC$z30Agl3RK6pXIk8eIk|d%nP7@6O^t82={}8gw>VKrj z$kgO}TwV&UFya&7@QE7NN_PvoO#W14V6fw`cTNoP8di`lp1(c$7buha{vh?#Q4{*# z5IZzgz8c*PHyEb1oMD?h0gZgh13%0x;=@#Qusca`^R`dSD0-BRO|lLX>`Es+44zQO zl@M7#1_=VqTOS#PQdP~T1nU0MCQr5S@X1>SM3zGME>ZbN1+Q^2?G;mDeyNy%ukTl5 zjI}*n{Z2bWaaa*4g0|m!ixq(Y4&3688ax@r55KPXpKpA{bp3z{)c@#Sil3j{!u;v2 zPVWH9%W^#xvE}JKoC}v!-)+wgtJm8`YST(PP$tfS`OHit`Src%ZqMl1g@i`l_KYQC zZlFX~ulAojF6K2|gj`Fn^P&=&eP1ss?NWfercR8NBoPU{pvLCRnE;mS6u$lvk=C^i`zM-yY4P9wW#r4+9oM>9LXh{ z*r_H2Uq3{7uaSg44J5Z)T<=?arxZ74)Qsq<1VF>o57=%qwPW3~I+?$JYs)n7 z_ITb*7#U|{n^@=BJS}W(p1m8CS}6#}1>jP7FLc;gSI zriAaaHq5^ZtW{Fj)YWIqvPc$v{Noz`?g1J#QO^@kcFCj$0*z;u(rE0f9D;$xA9u2u zthNZ#eLp!kT;%w#A-#T&zzwgRIEw23zQaxlhrT&-^gsFbc9_@OpW9S``=p<0;`1r} zQIBf2cQ^G9na;78QUmhfsiy#lDi8*5aoLr>lHTcqa0-HUMC?H&Ju=V&%!kfzO>Bw7d?hVv8Rh^S?Zgj3+FVK?KC_KyYa& z_R&*7a#sLPWRUyuQopS<$LBlOVYF}JwnT@UQZ#9Zy-XJ-e+Bm6kofaJ=^-uXWSXrH zHL#w3*Kiv3$C{m+?k!8wJ#AVDS$z;~LEMb#ijSLVFv&O6IR>jIM!Qvf??FeQ6!6uTAu$(yC%I9DihS?7vo+aE3ku+T@vj<}6gwe~3ZwF`U_m})skmH7cO z#Pd%pz~a-DH0iO~i6%&0RP5SZf*D!cTn~&E3|^xJEl-I;STd0?6fq5T;5QpymR=d? zSH^~HuMY8#jH?40b|2u5Lfj^3hMtx~fcv3}qg;*V*~vX)c~aQaTyyR5`T$P%@K8xZ zMQzwN^sGc>CO;!Z`VNy>30qbAmIdG&}Z=X?&p`Z*xBFIw@;o?cp|l2!_2eaa0LCLZ&No8O^X?SjCMCOEKLfs%Z05 za{3WIr@X*CjFbFx%T$i=GwIFMnAFyP@b`Sg=rKr;<_7&2exY=Sq9a(=E-bg{HoiXkh4;m<%_V$-2gJNS2v=*_$A%DN zv@kfWv9$}A$!0amzzI?O7!Mlo=CDp4?HL~evFy~@8A(PlQ!%;Lkfg>gQL<6Q@UdW@uaUYKQ(&K~gfDdpI4 zX|C)6vt=`*U>*b-MOJ~(xymG7d1fGs;jw#6J1-~G%wa2G20`6}N~A)+rm8UaoZY2> zGOG5WlaTbzV5R zRltakXO(ZqgTYJNGVs6e*yt>pde>NkWR86!82K=vFI;eJtn02*!ldB0v>QeV+2liX zguI5K`g(~HcJ~3WIcZXb>{Eir@@841+y%sIr*T#*@|;0;14z}xI%@d)d&($u<3uJE zKH#T3n?RP}gE^wqyBiGz_9K-*9B*|`YD@Z(xeV7`MaD@tLUlBV^`~mlGRV1GNXSKy zEma5?TFK|&2@mnFBvs-Ulhs_5pl7=;V1;}AvhMtU-F(S`(CIR9ccuJCd!psfxDuRF zZp6ebC5^H()1DI(`@(HY7`sEI3~+VSaqJ8IWhXm=HU;i!yIVe0$Ki5TW=m6l|E#T3 z6I^^~VVrFZByY-}APFgaa3KeoJ!m#Yq2u;ACEL`Kn4=szKHn^P@uOfeR)5hPD%HBjopq20L4)LT^Z|yyJr~Kv^Xl`5YBXELBo0DyV~R+12@9 z3SGF|JCPheu$RGB;rXStb%|2d(N*Ns>egY>^B9n1pAE9wLQVRqs)x%d7l4;Zu@!99)gJKb1B=P5=J`PZ zp_%EhI*~a@lPQZKH7_4CQa1WNkK=8w2La;mFSE#rQ$+lr&Cjy>&q+O!k+PlmX%+My zm<2b7tDHZK(^?eRw%T|KQvk3B+9yu*igXoh`b+CYRD)zhRQTV#4l4bHUpCb54{$D~ z|BL=gsDSS^=cKVv;URxB7P^WU7djUgg7}2bz940e-njkH1SgYm^2hG(RIqi8MPKX5 zS6mR=2{3Y+#=3uTMK3i-x9~7i+x(dj)9^0E<4Y-HC-;Hr9@IfdPg=*uG;L7{Tp`)B z09@?P;1JC zJfm68hFVT!$F;p}KBnZvnNO8)XDsdR!8leh|Fj5iE5T()ISZ7&<2N zEnSM=H>mkQP1Jd6oHasY>!pvVX-G6PAiqr~ zAp8h|DNFQoAO1B!0mW}w2BZ3z2#XTKR_r;PqWek5(Vx9#ZHvD2STaBu>|yW{^s3GB zM#7F5muxa)2l*#K-#T0)lS%U}I4V6mqa8Z6M@&+~&OUD-*l{J;&e=%Tv*y*B$bBw$ z&{n6J=AcWv&+k2$qz}u=1cfW7Ujrn4$Qt;dOOS`=~;5g(qI7siw9?Zw=pG1#^`t zW^7<+ocy4;CVJWU6xjm;O)isAhmbXk?-MN07gWNljx2;w4y{D0&Zm?v=|8;kD^~9w zaQ?NbT^EY(0eYcQ4DuyHx^n`Lnv7i0UG2EWUofb6VDJPNxhlUCdQ%R;*-Z zO{Q4|DS8ixhK443zn|%7L}_wqYh&b`DFzJ)75UENp%*gRjz>a3uH2||aKs0)K={7s z=GXeKb2iHK2%TP?+4)Q6K-x8Z%>Ws?h8@8;G(8AQfqIE3vxg$vaa_S%;R}==k)L{` zGG1hW)|x1A0>pJqF8B-kgDsS^$skY_8f z*fIun8b7CSLAd*leVNK!M+bEe2tgJUPV(^$&}Y}Gxj^{HC`&#veB?$bJMI0ZvXxM=4fUujFr<2-ujLtVb_rR}1vn%6)ny9?( zsk>+P=V+|*jK=}mYmR-;R$rQwJ6JSq2r`O+X4o~P_*5UsIm&e_m15rF8b}IGPSQ~D zcdMA*I9IgP2Bwl7fYJnOh!S`hZ^Ug8J=*q%9d%FJg4MFAB@GW}-D31-=aIQ3td>#; zr!p$XsIh2N_xo!@y8jbYN4H}LBwyo~oFWu`MCG8Mt&_jfe=MX14Slt}b0wMDSqj13o=_|jkJQDm#F@r9a$M*1+6 zxR#V3zfmnc85mNfN`p(PIVouZs7>-Dvp5PwFM6QU;&G9ZQxrVVJp~R3?))w_q`M~> z=6jK_bHUteKTEfc%+(&bUu|+Zryrp+-uq8(2~41m_Dz?9CjDKg&XB*2bt=9hb6ysZ zMsD1xwlYr{?m)hH0GlXTx!Dd{xO9zIj$Ikv%MW%^%vsjtMC{%TUH^?kDFyuN`JANS zXg@bg$R`SJ;{)B4)HszF)0RsYHMB@8xm@B?B4+l4U#+jE_~4!&2+laa^_s0trYU5v zS_3y7U5|w@v8>Yywk{@-wFjND6*)i@q&14lXBJAv6d zNgLaX-Tf*?yS^9g-2vYWnq)`05Wn(1Ys8k9tLgVYT4Q`;twYw-N{Q!M0W-B4>%qs~ zHFhwn_2-i9%|0rRzDB8%P+ zl63!iZggM-AtNZ_{|LMe7%TK#zaVsLt0xW zBYpi{S~p&?ux}QRH@yDfMa9q_d)EZAfNp)()+&P|J(FmIm?Lkh8&$jD zC3{Iv8c6bje@~9;b;KM$-dNnLIQS@XZSLGD)MlX7H6y`tz&dA}$a1Z+l|5{LTDd~O zO30&i!c&wA-l+tp9@{EAS8kY4)UJD@2-Qk{D?b*_B5e}o&FzWZo>sASHA`fzn(!u+ z72<8(H1*%1$Q^$iLy>2y=|@qr5qW`Bq>fe5?P12av&%b%?|oD?!?m9PpU*@xe0g-z{)Rli(v_= z^<3VVL}-quc_CUycDP%;mdggj=j4;mWOSb2S9`q-gV9YFJ3oy6*UVPN(#`!r;>Fqf%D?(g z>P=e>pWi;aU&r?~MZMfCS?(zX?+5QZA)>PI>d7V2QVyc{z&tK^|fn+Xon! z_)Y^JQ;hqZT8^E0gTyz7!l^_yKQTh+_$Lt@j#-KIYT`w5nju0)fR)3}*zRj6Nh z#Y8he-Ik>jn$m&a13t=>UfjHFteyM9V$|{~l%lyGz3f>VsM8t+z9=VjMP71!V!KBz zdRu!~S!^yN4Mg2zI!;`c`n_0&0t!n$Njd>~SMMg~&cG&?sPeC%+B8iO!3LHr50Td+ zi|mFl^rm(vc#!yA^!6%om=G()OJYpqVUg0NbaUhM>u-$P2R&7eAOs;D7Ez_8kraBj zM`ZsCP(~%NU+&;?`I-_59|K-OGYM_xw0a7*&Tt{&8H{VJ7kqYl6b>3n8GuQ@0wsLH z(RkBsYzn`^TU6zVD~L4}zhiCT^cHv#N?ec1)qiWg@z|{{4BTTd7Q7hOn4#;m7mEDx z)}lCuHJkRg@hVO@T_PmEZmFSgrD6!JN+jtO2lzG2DoRQrTiNFh#tndr+_(y+;-MgP zsvE$2H}R{Tq2NSTJQ{$a83+0XZzs?T>qaQPkkF1(i>A7Hf#%8sWb9zr=$3%;=_vhU z)P($>(y`^G_|b?&U;?32cID#+2~8@T?~)5A)BiT1P?`1TnJU%HBh2#ejF8Afh)|onWI3H2GsJICh)?(gAUPh} zN3EWLT|{KWgQ)#3%Y58_z4wFqT|S5TOr5BPkR@{dIV%3EjOMTVA`r2Jq08#_dVj=| z!LwUkWUH@VVeoR#GBui`=+KIQl2Fyrn=-P$UH95DFQtSA`=0&PKankK>}qE+g7Hj~ zK4|YHWQEvSID-`#8ejl?AFVVmEjcBl?C=<6ATQdJ#wh=cS>(!*O>G`rx3SfcR{mv zvBiiWXXeM^K)i21lD$G4;I_u)ysZO>UssBnn6gTD1odL`Gs0Eh(Xot}rq=t1J#*qi zdEQ>phsMY>catQTnNC`>gp3tQy&nq(vS5dA86)!E(1^TPy>2~0+k!LbJttzPZLvq= z>U4?~tfa8X7HTQ8C!yXkTOhv@8~5D(BVd9`|!zF@A9Jb*#wO+)&U;{^P9=pggA=C*}vZeUpiBH z_t~Wjukx;;e*XEz@FKUVUB)6~8s3$8$*S=N#fL_uOP5>DF>sSk`e!S^hsO$}sB zy)nicZY$l0jjhk}^_WzyUP?a-*Xv5<2S;vL%MrK6Lj%O*c(eVdvuf#MUb_Hfaw23& z0l!n+)7mpQ+V9q{ukWQZXo9h`hrCV--mz4(c;wyqtG~*iIYMM?UDw!H4P>rK`D#ZD zm?3GAd&wu`CzQYG!**^eUmzETvX^`TO0nI#HjZitMTEaK#*3FUel+4CMzadJAHWru z%1l&Y7S-{HYrv#$>dv>Fn(tWR3fKoZQ))}BbpLq7edlqzbH^C6&cx~z{3bRKvGth6+5;h^~28CCJo`G*JrmB&qNg^!7IA}_6dms z@&JE)v8#iZKUF2HCv?J-6=k}2wW-6SK$5U_k}@NKxU^P9kMS5o&kkj@Tu zWt=G{I{^}}tGy)~JuL$_Nk3 zAP=DUw`dDaxuZBa#i}lvya=7sl3fSIyk4U{Qct{*@K1|pr!v}IiQTsqPR1)A#9XG9 z!u-V7Ei-EN7kad?doEuEUwd!yCE&Hp6MQ5eXgfQy(enuLz@7Br3|jSvD$>@e2yjq0 zVmq5&)75LNax?v;|L>luFdAbZWbYa@G6g=7?_I*IrB`lb%{=J`D+^Klx|V+QHN4F~ zZBvmjq9?vk*gKI-Pb!?n$nI`_tkovi62LDz*X%Tli$Mv#@@?b+&dPqfzmCB(aN;Ib z{KN>24Ga8~_vhWaBjKA^DJq+iv!ZxAf-h;^pcs5yeqCRqF}96{rM`5nDGG6Fe5W#i za9pUDyvm>N9dKk$aEP^8D3(kB3mDOrTK4Wl2s7}71no|~QU~FH!9(HNf#tz^ATyrW z9SxF`1P_O@bqt}ZYqPk;4gLo{v>#K97wuMW%{d(L1~MxB*9B~j0-`yrd+I{pX#X|_ z8kD9u><^}7@S~pZ@LoJgua66YU2@DiT@3kiabN>Q9DRs=1Kya0RV#+wi;0WwLhg3# z;R5iE(^8UFh;ar-_f^@8_i*+%rz5l_AZufyC0Xnkd1u0*2@~k-CegRR@lw|6ycdnM7Y)qT2*G(coL>s}(CAN+t>b^te4Ox zdpEskuZmW0YiN6SFZ65rSy_?B_+_1w6vnAnw4zF_LpQ=q{;}hv!?;hLHN~Xf+Xo}O zW24?CEkj+Vy&e94n*)JrliYjtzwD79({BmD$)8W8!9em>JwR-$HQ#COm8kA1X}QtFu&3hfwVYzC&hp3Fh~e?)!>*9| z>et`pLTLvIZ^@RsZWydX)v1F!YTqi_u4nT7^$C^F7M8F0e@t1gs%PH6)m>HIVv3{| zPAiYCWUJ9*k|p=b@I06NaUphk@sSxEx*@*^#&cR-l2w`%AI_PL^Z+CkMmW_z77z9I zg!z7OWz4t}zri&^0Bb2J6x(Y2RS)O${JQQ{dC9~Jdv3l1&EMDqSyF$l5D#SEE)CUY zA5m3rT+_B+6poi2^9I#jB{fLI*SROL%S+x4f%{jv`TUOk$V`#DpfgDshHg~?e*tx-$m?vD?CdcAKswjh^;5xvm8TJd|fG|BYh0)?s#QETwKbdvn~OG))#12Caz;Uws4{kHq_MLpl3O(ZPol8r%;=NuO>@=jgQlw6zx zuk?~bfTAeBY65Ny|NAe&Xv^U0st!4dg{SdB$rcA?so!ZmKXJHW|Id=PS5<23n536( zWn>8HHv2Wn8~UWm+$}!~^l;^_@vDQ@!GYBnQTN|NLg%cDP8U9hP!L=cU&12yyQxMd z2~;e4o>mrDf}RXU>e`+HP0%Q5P|$vm!HX=N2$w;zrX|@Gn?^`-e5|YfYQpy~+HyN~ zN~%Kuyw^z={osQ%xY$)|N>Uu&E2Eu8uY>#Mr%i#f0b#~l)1D~D8dsKnEd%<#S>91B$gKt5_GbC8I9O^_^;MuwY#i6H+ zPj;&_b(OGUN5^DedZmU;vl^$eP&);v<~?ClNlhXuqn5dAQDc$-I%f$ z=WQ0{`4y@RE0AImUTQFitE3~YRv`--XJ;w5)&OqY^`luiMvoC_mG}*9K!;9Oh3I>I z*n#>h`l1%~4*ETr5bjgo*hRqi2TuBCF55jEj5tp7;zlV=Lj6IswuwR8>z8=f$E&^V zjo}=mgMjriGrWL1_ExQ)nSSMPQ(fE5a5vo@S6NRnA>z5KsM|)x<#Aq>oV0CThG$8F z@%}QAAGcqRPUr76r#Rj?D@%y4@ab({<5SKF*S>GNb4i7}^^+zPXYy(8ZS}!fcdLhH zeZM(Xz2|pSwWe5Q3i1MZWdmA2;r)0VJo#i4%Qf@37C$TTM}OIG$~h57$wlrkfdsY* zfF(GY>wVjFAn>~SZ0|kU+%`tKHs_Q}%Uv`>taKAB1E8AKzn_sk7=qeXK_~FXttC5` zVmDsVHgXf#jpxI3HYZ4j+oo2qJJOeT#rpLZx8Hk}zN^frq-ap$i}Quan`FB*@xs`? z`(S|PUJ>vWCfV{MdRE>m5$o~L?Jw|{4 literal 0 HcmV?d00001 diff --git a/Resources/ToastBackground.png.meta b/Resources/ToastBackground.png.meta new file mode 100644 index 0000000..1b503e7 --- /dev/null +++ b/Resources/ToastBackground.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 4b23b6267c9a94d64b94d18ac0f19754 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 70, y: 78, z: 92, w: 58} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/detail_bg.png b/Resources/detail_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..928543c004a695f430738731932630e0566f2e40 GIT binary patch literal 3114 zcmeAS@N?(olHy`uVBq!ia0y~yV2S`@0}eKzNYp{m*FcK1z$3Dlfq`2Xgc%uT&5-~K zG8PB9J29*~C-V}>VM%xNb!1@J*w6hZkrk+Fzo(01NCo5DJBHb@sWJ^8C(kxn=;Xbj zMNihqW1#|9tTpGZYy21T7rY2447jkgrKx*|$gc8tNwT{vEW_Vkxu&{H#7JY|63^LB z;?5U}F4@Na;=}KQJHOBST-VtCzxw=clOD0|qn9#%$?@^=-HVKkt*tkk9V<88jGcku zU)q~x^N+tiuI@iCCOSU;zFhtJ|6HpIGk-LA{QUFJ zZ1;Y-*xdyW56O3D7Ma@3e*5m-y7^Y6uYMcux(8JA&^0C8$k`YHY`U2|`~1_u+QvqPfgwXiUY>vR;>C;OflW%G-@ku< zPp_`7E}v&#Z^yv!A#2$(sdD!B=gZ2=_s`8+_ScDlA@1CjpyXiPcXxM}*Jkdi&rMH% zzIw8{f8CBVeERn7+tYz<(#^BY^Yh|%mAu?Mb?VgZ zese57J_GvT|L6JgPbzC_{@gxz@L+kry#2o7kB^SZFLQrvn#(BnpYzC+Crc;vo_7Z} P?HN2>{an^LB{Ts5)%yXS literal 0 HcmV?d00001 diff --git a/Resources/detail_bg.png.meta b/Resources/detail_bg.png.meta new file mode 100644 index 0000000..3b62fff --- /dev/null +++ b/Resources/detail_bg.png.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: f299b35e3cd69409c84ec21da0f6d880 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/success.png b/Resources/success.png new file mode 100644 index 0000000000000000000000000000000000000000..5b471497987289f196fb0fd9db99ae2c3939c766 GIT binary patch literal 1576 zcmV+@2G{wCP)p?+BjAZX%f}p4-`)64YvnwC`l!5l?ZKb92&@_;*#Xp zQ^~2l#Rvii6r4y@J#&d07qR7w}}b7On^Vo za$9Vm5o_R3Cp7Beut?1PyE@&-MncnUJyH2)q0F6Afp72B>Iy*OE*Qurg zQ248@WA;|Cq%m*R>QU_tBgTUmg8;yp82Rg?xTP$X3NYe4g}PUl;K(M6c|y!8Pq1_W zjHxpL5}t_cByJT{C?I+-orLCBaDF6Vl_;59U?;_$fnX|tAXhkvu8=J@m@FW?4{+iO ztnfr6)TW!Ppkx6%qfZG>kbK_?lNFRKARIOpH$fMkFj>J%7hvKl_?(V?Z6E^%Y^Zo` zD1i3xz{kE%g8y*lJ(!E1F@7W{B69TZ!vtLz3c#J%B>0p=Aq8%u@V$o*8uujp*XrBQ zER6B<^t$Z=3_b>*Lc(|KH;;G(mC`i0V@)rdW`FRh`G0fr9XR{?fr|F;Rx=<0m7ZB} zMJf1lse7g4I9xg&t!Tf#vI;*vdjT>~a)kuPhZnw6011Dk<5)#I34eX%Dae9z*2Qra z_yBywVEYkE;cu<3Nfn;J!B=q-WGeWCv#%Y1@lz)%+JF2z3tuk&0kXlzcAcF2B>SWA zHg)>liuUErEx7#KGPp#93yBe+_s~JZeCthAsJ~UXBjGPReggmG3*Zte!yR7%68NV7g*jlt*<)BQLqV-S^MhtHi7%wdFr#&%ERwUR>EhTuB z=+rombx7%2HmI9#A#SPdia8@d!Dk&hJ=Ttx-B`fqfS=>{;Q@klD zfS#4@1uGCBZqV#syf@{5d%y}7L!3{RH<%P)Sb-3Ziws*1^!@VDCoF4(qW2zAiA(~5 zMQP?Q$8s5@D2%EsgX0PUo-3T4vwF)?0c0jYas{0DzS?d2MXD}@*ZbmQdH|vV3V)4c zsrJ5YtuW39li2Gbv?2l~Gkx7Pi>3oxEG-1gtoL=Fv+L(*F9>JP7;3ZOV=114Bkx3b z!trJpno<%oMZZss=<;IB&g{#nYd3JcKR$}lVFdgbA~DqAcl#pK7aytTV+|deI`$x~ z;wFk;DoRmqNQD&kd9_eii{_5aQC;zNd7TQc0vdIU@wLL79ZescBx}g!^T@;en8qxjffLoc5e zLZ4pm|1brTaYX^;7%q}4bQ#51RF6c*=eJ!&E$c?Y;`#%l3lka>umrkT$c2?uw#VJ$ a^Zx_3-f{RG%|#Rd00009gP2NC6eC^>lFz z$!L6gaU<^`2LYFh`_px#qZAgf772M3W|lOmK3TRS;iRW!iBxtR>u1x0Ht{Da9PTs- z#)=jx7jZ6oEd1zRb)fZ5w#zSjzd7H}N{n}8NPfBZfxOJpLxEbP6J^q&)%_W3#7?aJ u-nFerT7qA@pw)ue`MK-<6%+K8rt=3Z5!2^6$9(|ka0X9TKbLh*2~7ZI)oK_3 literal 0 HcmV?d00001 diff --git a/Resources/tap_toast_background.png.meta b/Resources/tap_toast_background.png.meta new file mode 100644 index 0000000..55a6d3b --- /dev/null +++ b/Resources/tap_toast_background.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 011e7ad965bcfb549aeae5be34bbc0a4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 8, y: 8, z: 8, w: 8} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: 33 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: 47 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/tap_toast_background1.png b/Resources/tap_toast_background1.png new file mode 100755 index 0000000000000000000000000000000000000000..a757c7183996de560187edb10c4c40b8e012be29 GIT binary patch literal 1172 zcmV;F1Z(?=P)Lvo4~fU2H#l(M!W~W_N`*k6ib_?GDlVw4K)DoXqNt%Dl%Rxyi#PUq zH@hDPH^^@Ne(Xece6C2Mc7#M?AE7>h;$`4a`;r>wXM|Jea2nVBt(`!vn-Cj(7_CkM z&jPv~5p5Cz-w^Tve(r}>0yJ+zA~A;OkAde}`NA&qH7b0g*`8rF(N<#*y$$Mf;7D`Y zA|nmzRR-(=fq$XV4>i^kQmIkAU>Z2qsyYLl!M0u@o4r=AVSN+g@o})e1;#p|EMl&p z!YN#LxmKgvCfGv{pkxl%-AQE;vx?{`Jm;rs4XT?^ya9d>?CFNGh*?2}kKwvkD^*q* z+V0qeSZC=C0|4$t)L9tWS*fy;35xjUryvtu78Wt;0CE45N;S6*k@4hfz+2sxWQcA4?0%?+HUgyV;ZlU?fUq(IQzal`frol>Z(4@FRNK005O@Q$8#!`TvzYdAnyG|bJOZxA)sK7YHaUG`qKaai@5iMMl)$z ziBS}j8llJL2+g7!xKGW=#W2rd1e4sU?kK(X&2enIBN*~)f;ItS7|^l&;jU77)3uZp zRQOCK1EAJ$=(<-CeH2)&+Ge{@I|pwC(NnduTn>wklfaoyNsEZn6s$+;832t=pFJ;6;LkE(XMxAr-gAr%kasBL--JR#i^$Eb zAq4(;V%7|(eL%WtdEG^Tuh8;5Zho$r54LgMA^8xRlN3|_O`mr-P00KBx%pNCbmHX8 z{Us+~4!0ezaV{a8!jjL(=YMY}Q2Qp7l1%O)R8QYH1C<6D2epgh8K_ovy7E94x*5++%OEJFx2wq_Xfd5oFS>;3)V`xlhk>-M^x8qaw?uj_F=9{2nGab5T4 zEws7ehIKpEtyr;QgR#+Hm=!BlV^^$L+4k#d_?xbqZME=!Ydwrkd97HnRhIw1pNuhk zzQAApkzTWlhpq_xQ;C)m>j`M}GPI$g!4G%X_iyOUT~if#Y|0D^<#$P38a8 zl_puS{o228^}EIHS0DW?=Qw4(rR3k6_4QvI91b3MF28ZR7;`)&C4u6lEAz2?ZXqBc z{ocvqZ~gu$zrh_X|M?jD&$h(@lhjkhND3{x;Jt0!l$Ul>ZZn&m*i_e)=r>9Vm@Z|^ zrEn}VYwhN>v{P|Dp|f_a-0dw*?F$Qry2I@uSspCMMVg}w<8otH>df28(RY)1&n+LY zlT*0~+M)4=RkahT+pl--`w#E3ZrEhiB#+9SGv|qkKKWvp6HTE*JoJMQxA{Wf(7Q3IBcHrzg{@64i(Lim&#j@m z3)l&(vr~H?lvMRzrE&`iyrDY(dB0ZfE}u~OY`eQLbDe%o3;vzGSLS+OCxn^|FPh&~ z(>2-Bw4j?YeV^%ws0wS8)Ja*aEN{|oXK=@deHXtJu#;;&SiC`txf@d~E{i+1SgGAx zH=BGg)PgxVHGO#TGS9V__i=s_eq@4pQM2sD`9;6{U|#C8Vfd?Frr_^`c@{l0+^QFBViq zdd-!G-j(k$n^V!AS6+OR%KOyxj;rxS=F4F%&OzT0@q+oe0FEEKZFsSBrhSo{Jg*Tr z8CZtY9eLK~}?OGUZ^5nRe1qM$9`V3VCzT>(-n4j~RIPA}vVlV1e=q}La zvI4)s6QxRt=07ZZF|d*sDbL#�%zyjyLtg7=%tt1@S^>hIKh!{7(yhWGcqyWfmRh z7jkZ#am79#C<2?7zfT`bMtZ{6U?7l#r*M57>n^Df9k~3$`jL0h5_#xbh8vO{=w^BSJ4(l`q)BXz#w$c!JWt*UimGOs-4aA^OqCU+`j zUJFKKmrcmH-@@cn;9>4RUb<5Wp_XwB-d%`&{!eHrf2#REytdHE9Isv;mDg>@PNxtH1cd-kTH8u`1t z-+4VdbIt3zM~2&UT^8baEW5>t)Pp4#HmMwP{K z&Nu4l0FUR+8?WMqibqseJ(|S(Jd@~o?%z7j!zDjWoWEebSfJt?K;AWTFV}$Z!m~6F z8M>&$Db$@WWKY=R$dr zRo^0c>fL@Fs7cbb3(arevgdX@VNGs&LBPaQ3RNcN=6ksrq0>=|?vj#{JpZLjuK0F% z&_i=UcT8-6w_{@JPNws1Bh-O9nJsw>C$7_i(VNOAhiBY*v+g_=vp$a3%kkbU_`OB9 zPHvxg=r>q0epU06FM}Vq+(h*KafwZN(Jq~m5Y!uTX;;<)UH97>-bbF~4aKG3`nT6L z1N%Gc>p*R2;?Rrs_OBd{b{3jcryo+hf|;vAC{>KCc{sgwePM^o7wRItic_!4o6_a+ zemTc4NGn!+nX8=`P7PhuU8u2JI7Y#0c~9Ww$9CJT$Fd6D;w2Jq$5!_}e&iKfCAReO z6`@t1-;d4khEjP`p^Lp5?YQBqE%-AbvOe+Ax@n5n*ZTSS@x+&}-S#P!TL{TMGe&z} zVXmKAyr?~&_SSqv>|s-FlP*suU}lsax=C>5Md_}|Io{ytsI%nVTua(xjv*c4sD#Db zYuR9#ctdWnI`o@3FH-Q?FLT{v?Nr{>P|+h(<8w4i=~W1lHfib)`uyDFK-FS9j}v-m zo7iIE@~3NRYIq~P3rV3-PuPW>Sc8r$Hn8xSMLzi z&b+ooRpDj-lAl+wDL40ue*f{}g+SZW!DezXn$l6NBf^EC7fSfNyD3IRBbVYF)&FE_ ze^tCb(d}oL;{Ho&w|`o?N?K_DMb(_eg(lwf)5>v9A*f3ZIa0+7+6WtCW0N2SOT~Rv zm@V~bA-+2WUm7qtSHCjoZ55!&%6pRW2H7#y* zzw8}d-lCi!11%bj}qj_k)z3e<+Y)hRIBWBi_bp{PSd60=b%SfMY z9Z`F~^TO0Q1i&ZwbFHUiaxQ9u=w@r?7Twg;fUa{g*_B23W3QvGAXd}Xc%El{?kIe^ zgE`(+!e1CS&FiVHeTcd=FPoukvC+*#mV$5nbbs5xl!u**r=)b0fq#HPux3J>>W({i zCAJB^3AsJ0HoI`pOC5uwI27md7tCa`h@tG1nLG-li%2WS!(irl3+QmI8wxL zo6=5d@O)uh+YxKW8hzs zQ^v`!ZfKj1AXhGZ<)=H^rgdW%RgF&>;Z&owjkE&XJ<6^WoXk9@RBG|TTiYzhWbvBd z=9&*u?W!8<>KqES;=&)evS%*cq>Yce=IQ<(u6&|iW$8m{?ldu?_gjZT=i<|(-$+vF zd5m?$j6wSfTf9Z)0l{^Gzh3%WcfLR&+tu9D?I!9HCyaz33cuol|AH~?FRNT!<%u+v`Z@VgzH!$vaJLckMGy)aZq0pG=M|cr~PO)I{peamjKO$9 z*-_)qdF*&oxiGsBwyax8SERNFTM)`C0`}m@ZP#h(jrx=h7vD=mWCDtae*eN!W`DBz zfJ5QhnKhZ-a)KwBZt&1FJ!O=*&yKX;zTl*kc2t+v6eHHouzJ|;`PKV@VI%UD^z(!47gIS!q{OD-ueCCvZ(XY}w@d_=|3uZ% zwJ$z>B;i)k7DRh{yZtkixiy5wZ_*AF;SHQfR7!34tT?&%?n|Pf!HDHr6U*1CWQ}Ys z#1&1cjc#skNI@09CS6)q77*g^e`?&fyu8E3>c}BNSq6JeBkbU=9N<_p8(gw_E_h7S5Q+rs9**H5wTn6KV`o>~L`r0ksg`UcWi0b`2 z?(}$rS6w7oB#gg`)vSA1o7$8{GRfK>D(e3Ss zsvUO27ti*SnfQbMAm0v8!Ejspn<#sAjQe(q7Ff*b5J;dCkD_nAnjjMj4MrRbJAQ9K zpQ5&tobA!N?X2}ncl3!Uf3szHw_-ZY-qGMp`jgX-I(NjryQKDE+*g?UvoY=Y)sd1a znBy#uDWq9lf^i8EpMN~ZfcORK*tbRQ{7HAj%26CyFwPY*2<+>Dujk4cL!$(CP|8XI z)6@Tx75Twxgnh#ww2P{@Qjx)^EeJ*Qauj72El>{LcP{M6)MKI}6iMUwUoEiY&93&S zWZElj;)!&@xJ)vmMA+C8LH z1VU@CR`;qL+;cL2yU_&Z?+Y4{NW7`>wD1~Qa&VYigRpA!F7$RZ;YrG z)f{wkxRi~`)<{C6R5-%o=>iIefTFPoplVyt;)1`2m)C`xf;`)-WL8hrIc%?SLNm9qBY~7DHYpF*=YGP2-!58J(;{3p`|&JsnW+{ zJt=$ktXVWK$=s0Mt;Ebm8z-}=q3U1zS%s zQV49ts52p?{Uj&$mR2;U!{vSOiE!bloXL5Uxjj5upGb(3vfmwrz|z*sAcNkF`Ic{h z#JN#;y~5tf*65YdGXXZF0tF`>TF%uW&h$31i0T&&4Gr0X4>j*T@OropiPrLN&m8$o ztd6apNHOoC1|=^#>PQbG1R3sH3Su|fGn^)_yYS^x_|dtCo3aSVb6@M@REC6?362}8 zA5Nb();L(#K7OXuQ#Lj*OdEZxq@?6)oL$JRu5sW0T^Cu*_CIur7S{D$>7ouSl_2#n zD~j4xS+gQ)^=47Ub|-z6?>qN2=r954OT*qh9YR;ZIwFU=k1XB!HCv;?LHQWt(mYIB z^iZSU26GK!8)<7&g9vt1(zM<<6y_wdS*}(s8*pXkj7`N8Swzpwe`PB}xSam%)DVs+ za0c-X5Ym3vE6l$DFPg2fTws4102YWPck6UDolkhzmQm`U1b+3#8U zRzsWnQZS4Gj#a(cva94>laH)Mf*>`mE@vngubkg3 z{FO{6HgPDs`%(KC+Hnsq`MwT=m0L8A3bPuOH2K+*e(ts?v$@0K8ijzulhszqpDJPE zbvmXl{T_Tulo$BR((ce@&o@lPnF_$+$?4IPnd^wh&22CUA!z619@b?p9^aKo*iU-y z5rB+u8a`KQEse_|3)3to%QC&iD*MJfThYRbycxI7(xLEF<+eM=A%lyc@#S1BMuRG6 zp)b^Dm%B%Y7anfJB1PZm1k>_yF#lAp!~3AMR!=gv zBj%=X(#rLbKz0O6Va1Wwq6L)Jn~bmWjP5-tI!3#y&L=;Ar4}Eenjog0bSG9+9S#Bf z8~Qc5c=UlTga5rlqr{nj;Q#z0$VqzQ!Ln%oGA90hKp3N^=QSXeKTI573PMd!hP2UE z%kL^(N}7j(m*>ue9H+K6koI%g80aRg*9~^l>m;2WIOG+T zle2b5DEgR3w>w}_>Y9xBpNSWXPcuu7XPn4u-7Z?Flg|U^l<^teFm90N+-hV(zWvd= zKAA?eOG-blAQ;2uGY4L~kL?}IZ`7x~B}{a#Mi_2E0Oj5JcyJo%wMu8OAXNtYXj@i3 zGR>h-8jSA#Oaw5DB{A`;38pXIN^%pg?-i__;Fw607BS*$gCwfgL2OGVXZj;hhw;`1 zhZsk)wXN;)5S}33HUS=tKkQp9ZrOo~DSG9hV`vXpdZ3}<&UWG4Q`RlT~r+pC``CqI^u5a zz+;A+Aini_GqqT-(o@?zjL3!eRo#(Hxd>H5QkVE%-}jrzujJ6%`9&^`DY3 zK3W{z5sEQ6M~j)%>0sq*t)7W^HST*DEea&F9CU)I9NnQ%k*;V=Pfl;yg-VTh7Z<5^ zl?j@{#+e~u9$YLUbPqmb;x+TFj4eM4A14)XRnaRL*>(V;Pn$Cj8!+EhI~)|o3G-g& zMoS9anZ8<%#K5a+`W+;xpb)@hYdf9iV+2JQu*;=23;jVzm6E7+=H`y%S2gGV6I*X@l$03Q$0+xsh*$=sgo}_eP}_}Z zsOW722J_U$lpx!x&ApYuJI6MR1T=TcU~w}7(PhQ7(XK=t@j1GJU&AAVt7LPnqDh}Q z@AsO?=4*T;V)8A?e~M71{3=AoJ96i^ub+>)L?v%=zS^_&^WEch!>;wzQCp-1_XXyZ z3Fm>sZvt$mHwjz`w3LJQv6ot0*>t)Ux$PY4IS9%PCiWq>MOnw)#>VDI@8le*0q;Cd z^t|6PnhPJcBEQ;JouuLJc?(5B!>VI1ewPCxfU~PH$~=jv(@i+C_Za|SvK0i{DcX3} zTfwqU^TX6r$9>7W11Hsr$@bP$(6PRG9U@wF^!Tfa! zBWK|*6E4TlW4_BH)Lo2jSGy;?tq7J5yQ`~9UC-%eiwsjPPCiMx0lWRq@q)!PK`b;I zXo*8L1ZH}tHnWT{(axMha!Kn18H7G>q${Rqmt|T}NeW$~F#9 z@iFwbsU}@<)aQPb!se%Q5YCD-o?XzeGJ*pNX$ ztd--m^ne*KeFd?;B0z2f$->Ruz22I=hxup0VZtIvF2?mhQdP3n5uqq_U0!;qYeKAH#|6I=Nfh!v^i7tDh40EC~t|-8YnC~a}mZ6N#V?IjQRfPn9ZMD!177C9? zq5-IxQ2xrvYIOp2$OMO?d(YBd&_>W4x6YQE`#^wQMgnRemPNlLs1hpQo^bM6NgPer zV?MJGBqKdL;8@Sivs<58D|^*b2X+t!V(roa3y)39x+=I2!qA2a(<%Ry|*IJ z4Un5Vq46K&}z0{=7wQwF@E870IHnBTkeqt0Y1##vq%C zMu6Wg)y((4G^+=StCJB1>stMSnhN0%}#y*r+N_+j2Kf?Dmy#BJt4cu2L2+^LM8zX>R zsUibs^KK~8OXoVbBNUB{4H4%~J+c~K52R05$VH9-7nv%vrAAHQagDf64|@;ReqAH( z3v;AJnvjCFxRunGzVX)H=WpaE9qhZAUkJr9;WW=uF(-v!L*eq_WCKTMU#zel*2!!{ zp*Em$capIU>)ChOW^>KYv)Gr^Bt&F&^Wg!D&L@PdznFbx4>F}ligw$*)ky>AHKkrB zB_&yif@joa(7!?|t97KWCQ_2_T#Pymr;!>cEbpla0knXoPE9)cRsoD z6%2JXfn%_Gi4nDm7%8!ZrPLiEA2H-xy0Nc_i7$OeC>U(|buR4W-JAJ>8Jw>1(A=Ai z+Ytf6TKS{O&Z+vwh7Yc6qHUsi_PGjMzX+=EbFV|HRQik-zVsRFxP>A0#{h&PeB9%> z$MtW_>J|<0*83DZ6cDlqei*{@L3u((53#S*Q)5O6AUt7#y7~3M|*Fc>x-Vuh8`AW~lYyIVfz=9eK3W_Bu+JHd#7nJ*> zUco1Hc_Oxz*~Q9D*caNy$$jqE#>#bC^FGm3&i_&C_GNhN&*k_WCRA>;WWhkYPlmU~ zc&GeKY@1cWWwjZWY3wIRFWuXx+(}saU7a?`;W`wwaj9`a2J2!yb?D8$s+-k)ABTpH zi-HRUtjUWXDrUZ(0Cr{f0cLiKq^c2+!cnuv4e!Cl5N}9i?O|+3XxSU9_ymNJ&XV>B zsZB*oLVENoZTY$m6z*u+65c^oge#;1hH$jGPUivS6!wiV9Q@_kiz zF>y~|figX}XSX^fUP@g_3}|u{e)_({P1L#*6E6*F^L_84{M|hq+;I_#>1!>^R*ni{ zGr(R`>gKyk1kd}PN=;}G8H%BQGGn5p1MK0s;;xZu!Ie0dO1i?c-t4*M-dlMmhmP04 zZr3R&fdFcl=ZYTh-Bq25r*o*}=M(ZRwGpdr4X>k6r$MMlOm8XOwag<4w^4w^&Lm?d zvhUhD$px2AtVYmb#zSDy&ub<%bk5I2B#1yIj{X*%Gc@I|tm8j5WVYpkOJIo2Q}>>^ zpXyyJN83oQbBCm(ELBDPQ_g1uRGtPgB5P#l z%hY(Ic?#X+P=YC?!NKcSaj&6V>^~R^Hf0A&254!gROnPvFIKpKvoR(4ng19VPYuPm z)l;A9%WkkTW-2+oL21L9K3id%ZK&w|UFV|H=~MOf_0A<-jBs-w)YLb0&*3hX(k`EM zoA9|fZHw!)W4p*fqFN?#AxL^6ET&~bNvo~&O}c-IJHN>Sl^-qF=_ef9G!0-wje0;1 zJnX!q^DXhz3^69_7T4eM;JXmv28;lhqMEN)t&c#W+_xWyWYjvd#;1ISai!EcOADch z-7b)2zJsv`e0}8E41VVm9W4Wr*6NIRm+rMJuaHXP>$Iyn$mc<9)inq}Q(R#4L%+)^ z9>fA^;)J-Z+p2vXb!wH6;Pkc?yeaWX^Njl$IB&9B@fL(xT6EJI`6Sf6=+!N8FO&p< z6pVwCJNrDa3Y)!Wyd(Hcv}Np3ugqJsVGRx$`Xca@o0~g4&t1ejlBICuy;~zEd^VmL zMIR#y$*4(OTpazXj&dA52wA0T?1d?tB*tX9kSGRAy}3~km;W)NK0Yd31nJPoGE6hg zZbc9OIj%_{B&ih+PX)T3#6-txqwCtu;#>AP3Fi3|3BaXP*7HyvGB$7UGc=tNGBB%K z#pPm*7npH&{l4WDj!WJm#gGD5f>n>AqFQTa7X=TwvuHqmKjT#V?@FuW{GLBGe*>CsAJxul9M7E%QT zcuqbhXCk7hx2l~Hv*?IS6>s@vp6*OkuZ8*gdy9zaA5Ko6CeiU);_0odvCCT!48-?; z^4ZIBt>~uLo>Na^t|NLw%b!CPH}gSNNFqA}RY+{rgX; zYnH&5?qXDxmj{l92_|+CS-(miXj+4QVtmbnrGK}cQB<30~jmHBijC5UDOr4DnWrP8}BCqz-Euw!q&RW2s|p{V#B zhT<2YwhnqmGKq%vN8H$38lkw2UDdg}JXGF3J~Kfe?lliWJDAV)OBUA2AH=R6R?i+K zuvl>_cIQR0tZiy+Li>x)V>=5Ja+2^@f5l0Qlf*$#V4$`vizT4U2@(+Vsol&8`Z*Nx zrObA+SlO_Mdhq*N(5O5~Z6C4Ph)b+>C98gNxnvoOX>92bd{y(|>c0I{MN?ygkr?P1 z#!hWPj6A1Mnh80B89m#^#Fv$BQkFi5lBKHl$W$2!GG{jNZ=%7xt)GRX**RL;M$69k zcFW%ifW^I%Jp?*N7g~1ESkd1K1zkMNj`r1iRcxOtkoMvfMRIlwH$(`r6U ze^6Jj=`AB`tX8+eS_%HoGAC5nK;jc`Rp5r=^20C(X7QdNRX{vY%0-m}_0G8Q80G+K z?!}0|f9Z{Vq7`a;`v6j0oUV8dhAreK z%`%3Db|7@mFWp4#$`XT-K5Qu49ic3`UXSq>KAwl6t$#}h!p;#DaN863GOcB-wrLJ5 zFEeYXA=@@a2ye_Cvl^J>h#HRkDk~D7a-=s|w-=xu1?{=rWEW-d%m}oQ=pj{te%^}U z7}a}#5{{?6f&tK8&0IipKxUeM#=NvYIJwcfySw`mTadk{QO<1K8br>&&=x{mMjY4D zac0w9Xnskau^(Li>!ZDCPTnzoWqIY z9ddWnab-=Xmjp}@tC=;1yyPSk>uXo)_@OpRMkRzZk{u;{AVU~-kKUo&BF?CEdR|!L z%?$B)^K5^!m~mij%d`5Z?xLsm;+_&NLNcs%!xURb{UA0PQ7r7;eJXg?;lfH>bcBvE z$#tBLR2ZIF{}p6ED50@W?btAUVgHNkwf9w(J*q5gKLz~*+@rQ6^D}`p4hq4-n=e4z zF#uYwsHC`x-)2b$bmtD_?6aHj-!nF6sa-hX>4S2-K?7Xen0WW7Rw$P63Mfr7&yL_{@?1K_l}jI=ICOP%CFWH9Mb*0D$({;J5ncj}@P5okSqI=T%ij+R ztlZ$yu9K;lc&KZ_oWOd!0rYCHU?|mFSZ;SYkLB_jO4Pa&}S2cZ-sU9S+zV z{SlhoDMrlDLO`iPMlly@<|Ef2eBvLf z3aj~}K97@-fp(@rQi4R%RBiB>^&2^JKa;~L6SG!SSq6LWL#o`H0K&hAC!u=D?XQ0uQ zc|ySlDPdO;uqAQ7tT;uy&orENzTm$jznUF>{Pn61R@I5BsBWNkDXF1-%CnlG>o`dDU3ua5IxZ~$ax=4D=6J$&eH0S2;$=bypoeBH0 z-*8zA_v6#QtG{A4Lb$C#)H9#lP-F)*cfpH?iaHHJXcHo7%)zbu$kUke>> ze-!r%T1EC^oeK13oXZ(IP{QW=3D_Ip^b4wM^fEG4qriA#Vuq|QOGvSsiR2gNGj^3h zw;*>5f@~UH+%=B56de{rxDOM+%6sm;nViXjnfOmnRVejO!6bdNj4r&dZR$i#(k8fk zY0IZOZ?UX;tW8||9o=J5<`jp98u{e;TKN$j99iv(i8khgMD4&8P_!gx3YVAR=t(}; zeHjcLvuvqEkQNeBqaT2zWa5=z2dH*dYU#$(qM$F`TxU=QDBUeB+V1q;tyIgm6!jg~C81w52^SsV19O z13AxTmr?H4)|XsI1vCeL$lUL*z^$nXUq%uHEf(|~4>ZK3HVyYdZaC0mK??0=DKRVz zq4;HP9_lD`)G{(=Dhm^nW1<%8N?`}qzH$LJA5!KKgr^?|6%>rCanahrw`S@`I?k%? z*x*}klb0GS1V=}fuw}4^XL}xwg30lUHt>;o&<((?6%yNN+Nssc6*W>;B~Nj)Qo+8sv- zf5#Z?K!E}^?;U(;LN_06T!?FVCq|mIB8g~Fs^?t3!v;W;wBfmfBQmgu3=xKFc};i; zBO3J1^qD{^{X~3ssm&_m`PHuv5jM~Bp;M_(gq=nZ^s3yZLdPbG+6`G@a5e+9!N^5N z0SujN7@m3tyE}&7>Jl=sI2V252Uz<7|4Zo`kAp&VPDeUo+&55iqAQfRn*fo|ERs1H zA#5V2mDpAWB}BAD$irn08OW+7>7y=!%q#f1Pr0D<>PJJR9sH9}co17A4n)s14Bs*i za8p#4?i|km0XkP3%jccsl|&sm(wYCZY%pOJaUAZ^M#P9mxu2I4ppdAbQ?oJ>8ywC- z+iFTqLn|dc+B+upPm-!I0Qh=)lx|LqoT>3osK(=ErF9?Mk-*lr8wnS*)%H*pY;gdJ z{!AR#C{D6yNh2QDIVJdY4mqITHtD|929oa7`8nZ}5c@ph+d-J6p*jDvF%F>nIql@v z0T)~V3<=57VW*q4oR)^%Ix`l#6jTRSAodSl>&ytp{d>6Kla7_*cbO>UmR55<{$gwR{Yo(_hVliABg_g z7x!ad+>d>6Kla7_*cbO>U)+y_xF7rCe(a0;u`lk&zPKOz;(qLl`>`+X z$G*7#KfgF$a}T#jGj@i3T^Ii!oQ1uWy!}pjv#qEzHkU{**JqG6JPOxnU0Kn3{_R!z z$GQDyjbv9h9sZK{{NRPhX)lgQQ<*8N-SZmaH*fpvKjUJE5mX7oB3o&9^4;c{Bk~ia zS2FMYnak^7%&~($zON|bd6Sy!P6x4*d#^qSs5dk_E@41G+*A}lM6w7>+AsSkaz9CQ z?iOt7S_9ORNPow?e5Y8&8mk_uR1+PW2a^rMzWkrK%2s5xbQHs!y`_^oe+1jvLafk( zjk+6PduVPu6)Yx~NC8+OoxiV=M9)7t3R}hX39#k;GmBtnlZLseXvV<=&oTVMd@CQn z@X(YJIonnGFD|eL;IxZW)(zieB`bYRt8`4BndmfvEe;ozggcUK?hYH{ld~6`#fR@P z^A&@gadwx=KV%0A_CH@+qzvTQlqz!17Z}acHkrfC8k;^~<>q3SPxYCByH=8^+>AqE zM-3Xh%EmjhrftZcCxbEObGSp2;>eJNlddIamXA?o2apF7Dm=-m3i%@lEbJxiyc8YZ zd_$2b*w}xq=h90;QHrVDf%Q3F$6J0;3>@CDQN?=i)`AXHqPz?2;%t*;{Ju0OE2iJV zu33!dlMa`=_J0^=FO;<#Fh@rz&ThXM#L);s^O@d3X~Y%S=se?GeEScSt-fvC7DQlO zeLzM2cc;25)N%e{t0Wt9&%6lKrQtP^ID~MIYAt3yCjJ&2qb zgewkwJt^8t>NpcnF|p3#d=>ktVsOdi<9|rbaZ*LtjCY?t_xJ0I3!OInZc8J43i8Lg zB#2UZ-LO-9zMSu2LXvfUNznEaZ4!=&zYhKo&l0j0Z|^LKK+$Q##G}TzyjHMbSuLX* zlJ~T0{M4pr3!;-&dtgeg)AH-|M>|}eUY;ipqKNm}!z?x6hzEF%tZmqEUR!hw6dbx< z@7*&SOT7>e2iARS`tqBCw3zL-Q*3#Ql+4l>=M(C|7(*>fXzVZyhs(hq$;UZ@?;4^r z-fnxCFm?X0dP)>&-%Jpa%0Kw8HjdwWWvyT~xI|7ij8ja45k8#-r{}w^Wo4CJ!%@Hb z9TA>d;V8@!Eyfn$Akyb>rMbkGT8Y@F|JXNMOU;w9)qNgcI5T5&!eKW|0UNq+3k%OH zpFMlVpS>K2sx(ZyPiG^XWIe_|N5B8ux33pw`l{C~hnCr1mS*=V1)F;xx3}u}TC2 z3nx%)^-FE6$laHby*Cs`GBy293ZC^-I-G-Z@nd|l{cOgjt7nMA2?jVwd9o*(X05EO zY&tJkKGWt8SeMHgvE`m}7~&($U3F4wgG1N{132?GOd%fi>J|kl^}!`nYOF#8Vz_3N zO*qZDc+~mi@r-vL`}_YChC22^@#VR}x|sO;-Q&;AEHMrkb0(2;A`vb7JYa?rFhA=` zw3|i(7fB9 zMwQ!?1IB1#SYNaF=xpw~zQ4jz?{QgbIi~bX)#U?KY|a?9DhSRU=b3B0HBGxJPpy-%P?`E@+xg9MLT(oSsY~XouIEa0!B5*GMw&nl&)wynsKJ)O#W4n9N zV&>LK5^;u6(sZ!K*s(FfM>NuM>pKF$-tfHg6G_U02P`G492n`*VO<{kV|9dh7$nIt z(J>#?VJ3bwSv9G_7tRVwCVcMn2?#FImz}k^H3mD}ZS3su8X6j+BUA~nRa|3C&U7sd zkE=|I-!`zj$~3Zj$!7Zp`}#W2RF-Za%}-fHrTk&ErWfA2He{+#{on}eeg!S2EI(JF z`;hG|6EU0B4F@bm_Jl2v$g}4tkv^q+!nl`b9cP`R}j? zqjSc(?#ad$48~CN^ILA&mkJIg==J8ck3G{)i15CS$}_)}j@m?SbG77vk>HK5SD)is z1X0{9IlkA14vwDZz@_1zaQV_u8dtIEd_3F%t|n``up>|ByJcO$n@rbm2?Jt$)MxwM zb8rZ1Eu4-r8H4TpDMtdg3PzeU1PeYNqxZy?I`(0!>{CN6>@NS{XJY)*{4-BJh;w?#;g-KncgqfKM}=>e-k$kR?2Q7C=g7 zU6!24fxr9NT`^O5z8r3fancC>19~&VG2sHQ}3T|12Dco;&@}TLsQyY2;F&l%55D znei9kS8Hu;{c7aj9zjmNChe5HaGPuC1~vPVYIIqhk+}t%m_2{{j#c6&ub(Y7&fOlV zTzc4RJz&)_1EWbnJFPd5_M}lqKPCol7@0aPdRUEPmYgyh!*Xm&%jf!4iuU5fB6%xv z5oT1vGC?q2&35)YLf0;{d(qW|pqG8KXOXE9OwUq0>7nQ-XwpM{Qm*;h60Rj6gNr_5 zeBZi&13T_ezMlln%f;L(^e;Y&!pFCOfx;slCf=p2CE44{%Tfq2QWS5a$`q?S0|}O9 zBNqnPSN9*{r%Bu<%Bz2mSqd#Z;wl$8V=BGR{bRBgz;pxZZR3W~sc5OObWCx5+6Uz2 zpNTS}04+L#J@4V!nm_imJ!qpj3O{K)ZE%Md6S*a}!4O@E8*jM5?Z!9Unv zaeE9$UMOoVF>f&5qDr2j8HQhHxi#|GMoigDg4=HMIij!H-&xa3xA3pZwP84iTlxIC zDO~|vspqCgS_4Y0UmN%o@S%Yg$|X(0GH$x8l1| zbz>mSkErNlR~e3|iZ_0s@A}7H$+ZO&&P5`^;K(Nbbew2J@QE&i zSDuZlUYUE_M+{3*2lu*f zU(THsl_vMQrfj;|)Ru{?#ZGbT`EVMfk=ze_`#N7>5*I{g;qWv)d%(>@(-G;k5OE^s zsZwyK+e?CAM7~7+{#4iL)qdunmOQY4xT9V%{NyW2slhQySU5*s(cuDM2pe|Pjs314^*pLHvem1_l%;RWOlmAp)ey{4w6jZnyyGe z0_5xs-(wb!fk9B-U)LIZEbPMJ3ml2;1%$h>z-fS;1*G!xeu)#gdyj9Ik}@?+K?a|e zoPP)GJzN(Z-NE zcO8t5C;o-QG0b>4>zJoWF%-UUWW%bjGieN6@)$>SPWir^n!l7=?g_U}n{p3)@{_Sf zC92|k@KD6yb?|_$TBs6+!GL&K5MTrKyOdy`LIxlCiV39dfGdFS0uz zS!WsSV;KUU#^J(&0%l4OPo@LfX<2-C2`kK|(m*Df8{ZQt*nZB*aFiAkzYf=LT<}14 z(yiWFFy#UA=ImmiroPJRPUFno@<}bVU^fMZoo3yTN8^_B=&P*hK3`|*wzSW{(irL_ zYS($f=Dg3V5aZV?7J>de<9z9>=0k%3p+7{0#7ZKdH83)fq7Y2pjJ&<9-Oj|17#O{5;A41q8P6}axo2SjS7Iji@y;-BUa#_12aA91- z9l@~aiIa3L7Iw6?judf*lH6&(le(0llpB!pZ~IsE*TAXf#_hgBG!wvQI|*eN4?di6 zFxE0oO7I?L1(SI^j!p;#Tef`XNt zTjJhp1>U>WVDcy8*0vm?9h+zf^z2!3&*k>~dReVg=`EGg0F+^1Mb0mi;Dq;}bZn?D zMjpr~>L}_=GF&yRdVSIWIG?3FO^O8cc`IodkHUX!H(F<&xS!M|<2d828Z$9dp~nQ{ zKe}hB=}$B$dF5k*8EigSpcoA17{qy+;Q`4USd)R*QKqM%)F2O7kpU%kR|s5OKgECm zP}Jk1?nI&bZlxlIWUsuZ2KAg1C$`;#G;tG>&?87fU^doZ);KLXLYzgR)DC>TI!DLb zjnkwNeNUrMZ-TLn^^u*cl}0!Kof&HNKqlR2)UJL)emTng{P~XXNTED zoXD(>?6m()<@6&fEe|bs{rK;RVI9$-_;TDyZ%pX?c!`h8{T3^7W`9h^b(EK9v9+@0 zV^!%W81&1cI_2k?2I|FVgG1qEzibZ@!+dNi=&0DK&h^zW5@uh8T)D?XQ*3y6n4V!6 zphz6eL!VNP`$L_i(k6K9w9rv_*@QZj1e!r&-k$a}Z<-#- z&BDm>l~YyX9ywQVeGrKYhPZc&nMx*3CfEtv;XCmeMNFlz{)f*VfI;i%|FR>sjyD4i z#b8TO4x&mM(9o?3gZl8&28h~2=_tv^n=M1=@RTAlT}k18xe+8-V76`(_PdAuGstM; z#9y*3gZ0Fqt%;M0934gTXTe>g8=&bo478;$su&$)9 zE}xNX7vZ7F#P__5EyxVK(RF-ppqbDxjRRJELk-$b4HBA$nOHl|q63hBBvS zvlg7QSB}BK`y#iN?Pfxo=pO&s&!^f6z!h6w2O%eC!>jK+0~V+`j?Xi7ihbsuCdwpu zSpPF|car4Rm*UeUykfcynS%to@#w4tkRqL>U)apgyHGf*w|S1FdDM7ZZM}P&<5&uT-l({QW*2CTz8FIrnOjmZ4suT%<*&( zqmw)|F5ue@Wm7dYJzW_$6w^0Y(pJw1XH!*bV>KAwmKyaC1IR^yB_?2hy0M}T_{eyl zuYW+1-URwM|@)nE};AEK$$C`4{D7;6a>ktpSo2K@DKEvs|q*2TMR8p z?Nd`zoc{iPxy^LN@je^mwQydkA#d>i?}q3g8ec%@JRq};+{bQt)e%bxdb z0(HhU4>9pDSAX~wsR&(gns;WrQ?UQNU<2yeKJ8o7fjry5?KJkSvDRa*hVw^-bC3?M zhd?3Pqoj|Ft~1>lSyQ1OlVKSGmJ!atC6R~&0&hEb57dlE7-XKMQGc7wUL4hrjRBTm z&-cbZko-=RXp*)q>4tc_VB*hwPbxl=wY(?6ltuu=DAYAuxAnH3EL8+?desWv#F?de zZdwWEcDwulnYyM$KV7p%lKH!=dS=rdQL>@*xRo$`OLg(te=**>zKO=f9xZ=$_y3yP z3F+{{MR8&Uzw>{h()^ssCJI^jI+%bFP=lqxf7Jd+YLmOh`BVby`SW+NkJ`-0m`)D%A%sN}(%%Whn>-V;=~W=v{epO4v@f85?n;Z}QV+8_CvJ ze@69}t;M>f==fv!S;JO2E(b-bbVY^++WMOi)Jo5vJ`?Ol%p> zsbu;7T0^iC$B_v*gor@;C7v9tckpT53$KldMPU0ELl>KP?EFXO##ESU5;Oyv%KME) zEFd^epKov%8=vq>pWx#0`S#E&>vXNyYy9cOjgXb|mG! z=uzFzrvZ5J1NYq3@>)i>Y7}SQNh1!JmfGOxLCD_n4!|garFW#L-2&;b2LP}24Un_0 zp76q)hIoLtTX}}x7Bvxi6tfmOYpfJNeQuYgjREd_c^|q_N2H^aL~0E(K-i|+me>u? z836B7f=?Y4C%!0%yx@aEvcU~Xph>Ja(so1f%9evmwb^1!4dF z)lq&R1n@QND_8Dkah+q!pyo-0z+)+tEU#K%d>15mE6X;%)BSRj1(a+1!m_n~W}jM%?8XUp>5LM)biP#dpr{S(30-Sd z;oK zpJcDK>?hdy_zP;do(1z^3?cH>c>?R-K3}9(#CM}R3T5=lGmuZG*gpn^l=Z6NBuJ=*S4lln_B=M|#3#d} zX(yqrG=94tFm$lL|8_x0aO50*hl=!6F9+Dzxt)Xo06l3K#_N57cSe%Dhdu)c7Gb~u zStyymAPWB71L|~p!8Ut8cN%r}VNuCG$DGdf6uIOaA)n`a-9dvKJTp(;z{FXGB+EQV z{h4q+5S-Wty3hFtzW5Wr$Q2b~UKsDp8tjiLSFCaii=8-|GNIJ4eRd#9H|786@_p6evd8Y!(HxB!}r)F ziGfXb_RAQst-_H1whBh{ zFSC1?LnaT#l`5b25|FXkYHR!&5D|MvG68qHmz)t%8zcqnD_yR)n9YD#)%9Ud!-Nl2 zUv^NdDsdB_T9b1AMw0LC7&7$J|C?^S&iwpw2_-D6AgIMQO`;wT!*F$e_R;OlOTqS0 z(wKG}0yL2EK7CJ^B_1RqM5E;UOjW+$ydWH>v8LvuzR9;OZgb~%UO5bnqbnxX=RaTs zj(jPHYC}YIvIPK15?vWOh6SOtd{>m+@S@^75TJ^j?XqI{T9;6%%Fw)wS76=qETfXe zur0>0^knzLi^)=RMAXd0b#)5~x)H=9_Gy^o`@&`d7(S2NMNJEMYoxWidXgueX4;=; z=U-a^EAbr-xZcbE*WP!AHGQ^k_j!D)kF7$BSX_{*WlEu<2nr;Q$3c`KLmUhR5pl3r z2ua$arHG1300ktXvOXjvLNnGv0S@-KreNkt>bXFdH!fc02u0Fy1jgQPpHSx8(y62W1ORf+e zPy}zf*ACllZN?^EhJzNEdpKXIPz(3}Y}49)aVHg^B=$7}D0Nlg`tI(huIagBB-^HRy%a_e(TgmkXF zi1~3>*-8N`Bh3$sQW0lDDyaNFic|f_PVSj#EF#}(51l`I9UJ;*&Fh28ykY_5s^6Z6 zwf`z-KK2!hhWQF7P+W6FRwL)J_)QdHrlAIfw*g_ctWo? z!lQo&)X9a-p{++!zW3%=0ACBoVb>Y!>1$A*-7kN-aq(=X%TY9}EqKxv8V&-yA&AV8 z*At*I`{eLIX{V9eb3oe&)F{>u?FP=UwEuR7b%*A!&w9*^vn!K7rBS zt4^T@*vkj&v@Fmy*z)Y%D{s^XXDb@Uy{KCHlN?>;i{B4FI3_s z@P>6~5SKhE8stnPg+l3s`o#>M9=Zc|mD5PMEvpQhS(YR2i$N`@58;||Wu}SDjyNm^-7w*O2oGe0kiQ?eMm)t(0pM9Zbk9D{Eh6mN`QJV%)sT~5Y#p_ zpK*8qUCMReMbTa_RR6#}2D!BUoBG_j>5W_lb>$m;4&ZgJFHA1pPE~Hw#5Fdk4A+}L&^&3TDJP1r`JMPgQmiS_ujy2_0IGAGBP_N0kVz?9ZSxQ_` zbfmIbshGz+x0xqINk_iXt!vg1wQ;S6Azn7P8EA>PRp@xEN>#~B<7|#9a;*L3&o{>Z$)f}e!V#7+Qi;Ra_B#OYTI>#mY}T9 zS{(g(_XFZTQPih@p0^vzcPGgHHXSe zE}c?x-f~%#i<6qq_>Ix`{^*_yI&SFaE;vwruDwpH+ZGs`(fnxlk~q($Tc6QC;~rXQ zlzjeAQj6?wcN?{YIgKTU*th4m0JaUZMo^zMaIc|+jn8XLlA@+S7`3O+Pzg4GL#(C? zIK&#+!xC?I^8IIGIn=38p+O%iz@A=QyT=bR^S~=d{}^ycg%dvr?8-pyu*wlW0rfqZ z2sgItQk6k>+jkF&pV@ons7>FkyfWy19gCLR==oN%{i;E_po|Z2;Om^fIwBV&upZaU z>BUgD7N{o6HQaz&d=?>4sai3tz1}YQPr@z~sJ~20?o@wE%D)s@9@yc%C1bU7 z(@)w4%M!`OJI`UES$})mldib%`{!OjPH*O|OA9%N?E(IQ>wc6xesE3J;Dy^YdlqP|?QD&O?eY2y z=ppFk<=_B)waQw~8Ed)*7i=cDFMZjpi&&46(TmAVjwyt)?=#0T?#)5WIDvKZ{Ol7u zMhT)lSt*xo*_rof0i*^XkAUi=ge?f5bPR#m`jXVt_*LQ`x6c79eAF09YtyB2Ha-;U z{#^yTjtqb0SiU!to(Oy2#-%&~>>j9!ef)aI!Qhm;Je@XHCQXu2NIy4($*SmVi+M8?U@5jjRlP#WkQj$Q5KIdae4Oe68u zr_a#wO2=0bmbUVocC=PEP^ZBDn*N(cg(4zT+0eUy#vQ)@E21-Snr)E)b+NkW`$2ef z%vrc9UOtgBe?pwcsYg3-#kCRuBs#LFwgzR4l)1DPRTV82ozdAd};bjwAgCM$Bg zI}0_jCBXp!`#~_9`V2~#fCxo*{m2fUtHW}*o_zqU{B(4!M_)Q)P0TBPT5j+L`VJqs zvzuz|quf4~(;r(>oo)re%0bgpnkSxP`M;&xJLN}FV{RWS~?503<@0ymJ`4zj(@sPWNqaH(g-ChCC<5Bt35g^u3Aqkk1o6dekRmE zISXE~@*$Yd`tF2VmMY=%4l0tIc3zN6Mw{!Muo``ae2Bz3O|c}ej|RTXf4^Ki?v%A= z_MAbQ2Fuxk=W?kOWKtvG?g01%a083Gl2e18f_lf7)_7=t!mUB#^mel@F4}+_b=JA? zHdLR>4Wd<1=v7OpspYOdquiJNirwxZ0cp0q9B&5AC~Twr7%cwS(!DP0b3mc~K27$M zQx`wOffJT##{%J|;`kB2t(nukwJs~oa!5`rn_TpBfQsc8Wk~f0smFN}xd=bsHg*KS z+b%NN^a<10b0AUf?7LILMG?|nFE?%(qT{-r(}_e0O)anu@CD}u!}8z{d6oN zh<_HZ5Y+6YWZrqY(h(M+<4YY}dBMeiD?Z0O0HEM5wpeog_j3s4W43PziM4vs(b2;V zz`u`rdPK~PJwPvZ)5h>=OR2q;co!fUEARy$2LFt~zBp<43akPrABTBFKuN2-bUwUW z%b*bkZM+Vt72V6BQfb4C8<*R{f*s_vGPUe`x>m7dMX1{njM{chC%*csX_Fj89H%;l zv2k3}+H#QlM(f1J7I^)KVeE3bgQR>k1OR}E7BkKmsQtP&XkQHnbwx>ASQ^SO=|zpC z_}1dMhFtO1aZO);P3(0YF4-e9f4^Lg4R`FH;8~ZEKGQWyF2=IOC2elo$1ON&0f>~~ z)l}*P&;(A80W$-VryBzj=f!Ax0G@1jSGhwX))kd!5265U2G&wPsc4XW^}!;&$cHTo z)^6RcwK~r#qXy4Aic0~n1(|Uy#L*9*1KDnb0#g@BIqnV3}5b zc4E9g6B?i8r=E}6&Tp0j{s$6+R^sI2E>RC`8d>}5 zyUr`N(5H-cI~j~ieQggDTllg@QPn{~Bo&%>YT0qcm;01Kjsd$a5MZ4E=PjG{PeWMV zj{uo$cizr-sfaLnP_w(bb+MXZLbqe?=<<~zVo=0&kd>9p(NV_KHpY~m zTUU^6=A8xr;A7O+yIg6nLEC+EWQbcJI*?*?M4||2~eFu zGEMP6%*Yjn(;*)0n{LpggCL~2Kmedy@GrF^B0p;>M~PZ&)?|%&UdQTD9YM=O?N7naJEp`{^AQ=J0xi8$i|G=+2J#X(`+m>w zq>7^Gao`@yqqJ!;0A9P{ZTKuiKE0#quYj??5NF>wb;QsFCz<29`IFDMp{D?XDtorn zF)b<%de%a5(x2c~)P9vl2BL3xZ@%0adKPADSfBl3;1fO#g?bVANQ`#R%XEe_C@opN z1|5&g-#OlM3}8w9CAeG%3=8h@$UZCBY?hN zdb~g)(^mB#1ZsYT(U8r*rk4t=lNoJ)gz?nkY)AYV^Dl4z=+F)3P*FiU?R{>4BGgF#R+%=XI4ZOgD4`Kwe^vX%uB|BRNbbW*&TJ0O=w zp@0rwSb z?f{c=Mlb)J>owUJr(qlvaL?qP(Z0sj!>s7Q*p6+`(l3p-qG0oKed%p#FX{!^(mgr0 zv2cta&V?QyUxbCv0GzXJZ{vatZKbgX{SO#J4J;oYXs%3LUJS!am;BOoOPSspLs>_fSb8zwuHT#W@Y z-D)(Y4cNYJFhFP114_Rwfi5t6{zjsHi<)4yGLnc#V3bhyJn-%L%`lbb>32_(11wvf z8+09tfzZp!6tE2ziIbiJOx_WuE;vc~{c;^INQYmn)yJrNWL#q%TW4?rf%3A!6q~oJo@< z5Yb_jx;^v6(jl_!Fjb6H}JT=@F7;vCZNA&nz-T{FVO`#(-roT~|q7pJm&Kd z-QS=&-mfhwe!O2{>-oss{7V7qX{z~ zgG``g`+EGQ?i3rJ$SNMk4h!9Q z$C4nPnRq;r#}MZ+SQTBE+pIvp?TduHT!u6mnKmO1m>I!9u+iLI|5s#j zgK6l4Usl<&Eq1viJYK z6xXkQR!(lqldIy(3Mtwrl3)j9|D?S_|9H_nN+#ckNHa%dLDK#1_E>jjddmeG%*?WG zqLZ_nVD6`CQedqM5|=IP9NrbmOW;~hfJrJMu4Nzs$nqbtiF2FVdb|ofxiHi_yl(*Q z_^V>Gk&-;W`*ugH`I8fW>vcjWQ%K95H`OXIR8ggxS9+ zJGNH~b&)}ely~2^_#C!%&+|Po`tXIF>f6Wx75lY$@!ajT2SKg}O)`z0%>&Xjy*3(< zWX**q=OH;m8ugz?fs|9cXp;GXbWdkMD3S>H2%j|VFKfZbBqW57jFY7C$oA`ch0hv6 z)Di~B6UZyQzqP&;<1lw@x0BTb(L2Y%CypyfnwR3W+Kx zZ58}2zy$IGgzkatbQZgy3~hCweVpIg$P5$i8Ov+NYoZ6Lig^0*YdIZ#)8YKPlp?~r zO8y^&m)~f~Xfa-#qav#s{9q>!*U`75o?$atGCsZ*bKscphEJZB*!3`)H1CKxEK5~q zMow9@GVx>cP0WZfLCASpML6+YCFc-5DRfkkw zzD0r;s@9Qyf}g*>{sT97@X-&zC40-EysLIl3C_d5E5AQ{nNMQ^IaX#RdATRE^)0b> z<9Dh>B~V0=$E8!oND#5KSlYo5yS$zJxU9C*EV}BfOVrQ8gIq7#$FI9zWbX9ddcW9@ z{(FdD&ux$Mc*EkM&e|2n1kHk*M|*Dz8u*Sql3sl60I$rqk-14wn@K>Vf8adAZHSbu z0w%@uS)31NZReQP;Yl>xXZNHukQDh+nkGu0tiR7Fnyc?*OKO70Z}9bt3l4~^$P{gk zF=q#&IX&55*;d(4xWR1aSwt|#gOfNm5|IFXSahnJb40KQa~Y3_KS)OMZw+zzFQpHQ zk-dY+gIs!2Bpl1$HuO>L_HE73`H4fN$ikeOsJsc+_6&0VO_#Rgl&a~t66g|=ASX0 zgP~;@_wWFB=?dLJf#LY6WPUPVW8=HQ(WxN=ilm{fk|d(TV2D}EtQn3(dm20-lcFc_ z`j~;LXt$^seQndJOrrkiZ0-{;@qgg&9BEZ-Hf|VjS^) zqVV1q9GvTZz2%M~A`j12=Y)nAz_anO5+0lF!{JDif8!-8vMdpb!LBMAn`bvO9qB$I zQ4`0$bmJYiU;~n&hrAT|KY$O0uO+$e7lrQ;`q3s6IW(bd`4E%uC?&MvlPG0EHk&;3 zk^iLfu)s?5cAS*1-26<9|LX&|Ho#bYux<3dwR|s6^>Q|UcddrE>sZ%D1R91Duf}dL zx+up|8)}9bo~3HZ>t3b3XvVpMrMyo|we-r)4TLupL?^@e>9%Wzf%D$(DXO#H__1cn zbB6hxH{FHk=7(a1pYOt_ZymBCe(0_om_)}6X%XL5>?FPSi2RVQN=%C46%HBDi@Yeq zfuf3f^r&vHWU{X;rrws|GKp_svh$G!=438EM=DcAvM^jLt%!8H`9pT&&7Fx9WTGT^ zM63R`)r^ITZe7zd{;aUw$6}6NUw68Q3~wmgSUe{zJ^=Z_@yOYrZ;w^!k{{ zP!XyF$c$tiqDm@=VK5virp62V6`@SAHy~9)M4xjP0!T6oq6PE36f*%~V^}cUCuDLu zpX|yza`bPbl-VKP)TyF0K;Z8CgDTD-31iB(VFh@Xw--1H*Sh$^8zJEn0hWMd3K8_G?S0NZ! zL*XYd@mDyxOpmCfgiaJLYbr&GNeL;K633*AYH-}qDSxCk*JsacMg2H#5HIXMi}Xr1 z{YKz&Ml-mM%$Z!q@iKH^f!%xy-CSy@J3d{zj42W}aQJav%$e9uF6I!suzGvwSTL94 zguWm}iicLKN?&RTiFWR!!3Q^y72hpHNya3}QyxuGvho_SV&wn@U7laZhB|Edxi zGnT=U;Td`uDac6^J&s~-71#}Z zoL}J~)!-I-HCGiHtIUxC2ZO`-$I*5~$`nzg#VvXm$?!T#IJ_3 z26MKy&#GClyHw^wy2*|6B9+9*bYhtJ{!^+9hOr9CST!o%W0S~2Q%0Z*Yy7$Ickt(1 z;wwBvcmpIuXw88Pr>RLgjIOWb)K+oA_!9K2<~pPfIlvVEJn;&SrLU!p2jK<3x3H@HPxB8KN)bcRZ9{G8Q; zbQR(^UE5vXJ3ZYmtM5fVy_if{0UuPNg+{9IggHl!c?keIo-Je z$snh1!YpKBK7_$?Bozbe9_bty?& z)e6_RAVt-|H%4tArp}dkv{p)rNsk=)fe~XBr5}6gLPPF!jo@!de5D=w1hIFk1TE2L zo^Ta(P>3(% z=ODcO_}5qUZ3`I|?rGuc2FcBR^nG`aXyTJf5owLAi1K{X5AM1JkmZ>HhIjT%?dG%d zqF$ChWbiAH1_8!Ah`E9@Q1c>sYF>q+&Bx5jFnz)4Rx_&(r%N13k|#{upzuQmzpq{3 zM2^#(x8zQ)AR4m;kz)k(B#mFks>0y~KT?L?`|O!YwG`82xt;3}T}m@N)p)D;g-4#P z0pF1H2_G>bP*dh1B!LujR!XVJhF6d-$da8Q!hX8$9-#W{X-q_5ecW&OQjMkKH>FA%p2 zz8NR>;D@wC#5_mRkQXU#E2pnefR?PJa0fZ%5g(1tURgn$&Bbf^B{L(1ZbJmFY%8N^ zq-I)+khF%9yJz-hE#}XTT+ZJID5mQmKwgw`tYRh^NKYN9RIt%R7Ek<69uQ(UFW^hZ z#E=T`UiCuKM3Ev!XsEIUy`l9obUhtbhJ*JT!1PE~uSX7J;-G#)sv&`?#!wgrb;rB& z`Aq&YfDWD?210 z#?-%4jhg%B6-vaPYm}sm$4O26@pd1}AQ|P8VE!;{2f=x1jNN>??Bvh}2!*-Kh{31< zq((56JvqM)$wstbJ0q`6>kt&*RUQUr*T<*%ODRHk*-knNQZJ_(b_%MzPKXRVybLik z8$ED$n)Id~>ElcqKlMTx@-bnpg?BFSXYGWA6!8?*Zr;n$(&u0G$XPtoUFD()W=T0{Z%tNKRt9tCLkUk$KMK?`| zCN>3MbEHh_roF8cRQYd4S%fhuQdQP6&YNEU*$)IHhr(;;7;PfH>z5Eep(+A|y>-aE z&raB@b`?Vhpe`VZ%>t*)yx}nmVkLZbvk)2FU589^KJ?E1KfQ8HpwK-j8sslXw)5fY XDWsn?BT5J4Ke9e(`|teyXMg=4(FD1v literal 0 HcmV?d00001 diff --git a/Resources/taptap-bg.png.meta b/Resources/taptap-bg.png.meta new file mode 100644 index 0000000..9791903 --- /dev/null +++ b/Resources/taptap-bg.png.meta @@ -0,0 +1,140 @@ +fileFormatVersion: 2 +guid: 4cffb6ee31c7940a8b4c1c93da48dcbb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 300 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-close.png b/Resources/taptap-close.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8c659417cecd2c1cf2205151cd1e00d2b7ac68 GIT binary patch literal 565 zcmV-50?Pe~P)|n)Y*Jbv>4KC~IHVZCM|d+jCh@*07%%2&f-^ecqOJSJu&T0>aMz z?gxAMvK3vMPrGhI?1l){)tm@O;{qPX8os3~r z1jL3wa~O04N(-ll4qbSh`EQQ>=`HpV(AlO zDn>NK+Q;TJj7W&ucZU>=D2Ubf*8R=Z*Mv@0 zdxr{F__p2WEQG$}U_VuNs9L(H3#UpcrIb=iDHYK--I9*%??M{|00000NkvXXu0mjf Di+t@A literal 0 HcmV?d00001 diff --git a/Resources/taptap-close.png.meta b/Resources/taptap-close.png.meta new file mode 100644 index 0000000..71ef1ea --- /dev/null +++ b/Resources/taptap-close.png.meta @@ -0,0 +1,146 @@ +fileFormatVersion: 2 +guid: 51992479b22bb4d6086342339ee2dc49 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 300 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-router-v2.png b/Resources/taptap-router-v2.png new file mode 100644 index 0000000000000000000000000000000000000000..313950ff915c12c7bdfdb71bcaa4f375b2c9705b GIT binary patch literal 1292 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 BAm;!8 literal 0 HcmV?d00001 diff --git a/Resources/taptap-router.png.meta b/Resources/taptap-router.png.meta new file mode 100644 index 0000000..50e0e6a --- /dev/null +++ b/Resources/taptap-router.png.meta @@ -0,0 +1,146 @@ +fileFormatVersion: 2 +guid: 1f1d75e0e3f9d46c399f58f2ef25e4f4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 300 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-sdk-refresh 1.png b/Resources/taptap-sdk-refresh 1.png new file mode 100644 index 0000000000000000000000000000000000000000..36ae8b12efdce7bd2501c6e3f206861bc592ceff GIT binary patch literal 6252 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 literal 0 HcmV?d00001 diff --git a/Resources/taptap-sdk-refresh 1.png.meta b/Resources/taptap-sdk-refresh 1.png.meta new file mode 100644 index 0000000..f16b642 --- /dev/null +++ b/Resources/taptap-sdk-refresh 1.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 5afbaa34df10d44c1989b6aa0c25524a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: -1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 1 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-toast-error.png b/Resources/taptap-toast-error.png new file mode 100644 index 0000000000000000000000000000000000000000..6e98d258f11b5e6ba08c7238a648314e93b5ebbd GIT binary patch literal 585 zcmV-P0=E5$P)7`W zLa0<;aII>)6r5p_JRSD?Z|<^iCyP$!5i6DVoiH|IS_biz?|2nP*Vnf~h!@zyjMg{D z!(lH2Qp{QhT95dzj)EB}kVUl=S4A;Y;-`obj11p{0d zukjwAae%VcXgu)10#S(joDZZ-3n3eW!8C=YFkXvkkJaa{0G!(|r^vx-HHS@8$r?cM zwW-77IgM*VjC>DAZZ~mpdD${S@Ckm)pgbvqS#?2hDxkznVP1vZCgOTsZGmMmV`c2N z8<%SScmewK*<>v73~mqcW&5W}2l9ZBE=xL{z`EnkvLhrcmDFGdWiQy6Z2%G6=f4 zKT=L|qI({|LVZO5T3b3wxNVF2(Bdl*&DZQ3NVS3ddp=#k22_^0t5x8rQIFWV## z9}BhW11WOzE^P{1foz=YRC;YLN!)C(V}8V=DC#1;YVocYm{Zbj%UDYLFm=i^AH=mk XV~7#J_wuXL00000NkvXXu0mjf1Yq?Y literal 0 HcmV?d00001 diff --git a/Resources/taptap-toast-error.png.meta b/Resources/taptap-toast-error.png.meta new file mode 100644 index 0000000..aa8a08e --- /dev/null +++ b/Resources/taptap-toast-error.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 681b1fe2004f841a0887a03a246f8ddc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-toast-info.png b/Resources/taptap-toast-info.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c65d58a926b7f021d795659f4a4f0fdf99a1ce GIT binary patch literal 510 zcmVjqdCXkP|>iSj*CAj9s`OOxy8I zDGi7-pzu##r_-7II&bE^HwYW#RtGLj!A(03gkkqI)Xhf~#Gcu8fx@S*|6_Pa*5(%P z6MV7}ShyOMg@GLq=z?AovgZ5!@$K?QwgS5zHw0r(Tfn3|vTmU=ZoqFVJgkhWfUE=m z5GKjR+e0bHfwX6y#jRfU*ER8&7aeM2!+KFLL)z1#Jh0SdYi9T=SUS$k^T@Eupq6XS z%p3V)%7;K!N6-n2LI^-q&!Vxjw3Q)3?tbcUTev)*XYm8(EP{3)A{K)^i%lt=U|%8@ z%X0G>cGoZu8_L-}?5?(aq`=6Gs3VlF+lJjs$do7n6C>vR94MEXps1E5OOJX-Zc>puYmX0W(*b35=Q5jsO4v07*qoM6N<$f&>)T Ai2wiq literal 0 HcmV?d00001 diff --git a/Resources/taptap-toast-info.png.meta b/Resources/taptap-toast-info.png.meta new file mode 100644 index 0000000..e2a6cf9 --- /dev/null +++ b/Resources/taptap-toast-info.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: d3c85271637db4e658c22d67999b74db +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-toast-success.png b/Resources/taptap-toast-success.png new file mode 100644 index 0000000000000000000000000000000000000000..5b1249bfa951455454726dc9108911002eeb017e GIT binary patch literal 533 zcmV+w0_y#VP)@(tlCJ_;GbuyDM!XZKk=U;Rx1v4oxxDNZkBqAPyzX_-mO3;x<14P6&?5ya{q=z`%2omw! zjAD`EF@ElXVjDfMRkS!kikDuWf76MIO`8^5BHV}~q9=jLd5f*G-Vv69JT2(ftKBz= zNPIT+>besb&t@xI$^oZ%A-6&H$OP~E088;q%B#CM$yhhhNK~??SXO--jIA?GY{O~! zIok}h=Jg(7d9N)zaAh>WpBcEO2su!Ob-=EGi{>XllQtIt4is{eh&xYGWihMx4EB`q z=32-3*S@$2Ej~BtAdU@Wj)=30n?f#d1$B6+MhI;LOf^`Vun&Fi-fmX+&IGP&-%r3V X*B8VTTnrM900000NkvXXu0mjf`B>X= literal 0 HcmV?d00001 diff --git a/Resources/taptap-toast-success.png.meta b/Resources/taptap-toast-success.png.meta new file mode 100644 index 0000000..7d25d46 --- /dev/null +++ b/Resources/taptap-toast-success.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 2d5e20c49ced14858807cec65b2e9a84 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/taptap-toast-warning.png b/Resources/taptap-toast-warning.png new file mode 100644 index 0000000000000000000000000000000000000000..1d352d1be13cdf5ed48402f94349073259d9c39c GIT binary patch literal 466 zcmV;@0WJQCP)K~#7FtyR%c z!axvxcL_86z?>aG2XJkgBbDcd1UTX%e$;4dKb#qDOE3-XzDVA41D z)pEr7uF9dY4!UBXbr!NoBM+GmCh0*2Jyb)X{W)^fY|Avnzag&N)s=`zKfGL-bh!p? zBr7j(9b8D%%V<5Qt&%u1tf0oZpqpB{-Xtu%#55cs&9YbtZprVmAzFIL0j-!5dpWkh zu`io2v;$A*=_|s<+#?rTv&aQ^VGkA(*c)!q~;1)XSS7LjX7AufxHv|yp7J@+#AE*1PCe$p@C3utJMH38{z>%07*qo IM6N<$f-J4R#Q*>R literal 0 HcmV?d00001 diff --git a/Resources/taptap-toast-warning.png.meta b/Resources/taptap-toast-warning.png.meta new file mode 100644 index 0000000..5c6fa7d --- /dev/null +++ b/Resources/taptap-toast-warning.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 5efa9271d97444048939c99c03e360b6 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..eefaf1e --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fea7be9a2fd0c4b54a5875697b4173c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal.meta b/Runtime/Internal.meta new file mode 100644 index 0000000..3e43062 --- /dev/null +++ b/Runtime/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d7b48bb0f4b64656a6b2bdba97e0058 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Http.meta b/Runtime/Internal/Http.meta new file mode 100644 index 0000000..3c40b01 --- /dev/null +++ b/Runtime/Internal/Http.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b623eff04d7c84163a2ab4387aa8206e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Http/TapHttpClient.cs b/Runtime/Internal/Http/TapHttpClient.cs new file mode 100644 index 0000000..b204db2 --- /dev/null +++ b/Runtime/Internal/Http/TapHttpClient.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json; +using TapSDK.Core.Internal.Json; + +namespace TapSDK.Core.Internal.Http { + public class TapHttpClient { + private readonly string clientId; + + private readonly string clientToken; + + private readonly string serverUrl; + + readonly HttpClient client; + + private Dictionary>> runtimeHeaderTasks = new Dictionary>>(); + + private Dictionary additionalHeaders = new Dictionary(); + + public TapHttpClient(string clientID, string clientToken, string serverUrl) { + this.clientId = clientID; + this.clientToken = clientToken; + this.serverUrl = serverUrl; + + client = new HttpClient(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Add("X-LC-Id", clientID); + } + + public void AddRuntimeHeaderTask(string key, Func> task) { + if (string.IsNullOrEmpty(key)) { + return; + } + if (task == null) { + return; + } + runtimeHeaderTasks[key] = task; + } + + public void AddAddtionalHeader(string key, string value) { + if (string.IsNullOrEmpty(key)) { + return; + } + if (string.IsNullOrEmpty(value)) { + return; + } + additionalHeaders[key] = value; + } + + public Task Get(string path, + Dictionary headers = null, + Dictionary queryParams = null, + bool withAPIVersion = true) { + return Request(path, HttpMethod.Get, headers, null, queryParams, withAPIVersion); + } + + public Task Post(string path, + Dictionary headers = null, + object data = null, + Dictionary queryParams = null, + bool withAPIVersion = true) { + return Request(path, HttpMethod.Post, headers, data, queryParams, withAPIVersion); + } + + public Task Put(string path, + Dictionary headers = null, + object data = null, + Dictionary queryParams = null, + bool withAPIVersion = true) { + return Request(path, HttpMethod.Put, headers, data, queryParams, withAPIVersion); + } + + public Task Delete(string path, + Dictionary headers = null, + object data = null, + Dictionary queryParams = null, + bool withAPIVersion = true) { + return Request>(path, HttpMethod.Delete, headers, data, queryParams, withAPIVersion); + } + + async Task Request(string path, + HttpMethod method, + Dictionary headers = null, + object data = null, + Dictionary queryParams = null, + bool withAPIVersion = true) { + string url = BuildUrl(path, queryParams); + HttpRequestMessage request = new HttpRequestMessage { + RequestUri = new Uri(url), + Method = method, + }; + await FillHeaders(request.Headers, headers); + + string content = null; + if (data != null) { + content = JsonConvert.SerializeObject(data); + StringContent requestContent = new StringContent(content); + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + request.Content = requestContent; + } + TapHttpUtils.PrintRequest(client, request, content); + HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + request.Dispose(); + + string resultString = await response.Content.ReadAsStringAsync(); + response.Dispose(); + TapHttpUtils.PrintResponse(response, resultString); + + if (response.IsSuccessStatusCode) { + T ret = JsonConvert.DeserializeObject(resultString, + TapJsonConverter.Default); + return ret; + } + throw HandleErrorResponse(response.StatusCode, resultString); + } + + TapException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) { + int code = (int)statusCode; + string message = responseContent; + try { + // 尝试获取 LeanCloud 返回错误信息 + Dictionary error = JsonConvert.DeserializeObject>(responseContent, + TapJsonConverter.Default); + code = (int)error["code"]; + message = error["error"].ToString(); + } catch (Exception e) { + TapLogger.Error(e); + } + return new TapException(code, message); + } + + string BuildUrl(string path, Dictionary queryParams) { + string apiServer = serverUrl; + StringBuilder urlSB = new StringBuilder(apiServer.TrimEnd('/')); + urlSB.Append($"/{path}"); + string url = urlSB.ToString(); + if (queryParams != null) { + IEnumerable queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}"); + string queries = string.Join("&", queryPairs); + url = $"{url}?{queries}"; + } + return url; + } + + async Task FillHeaders(HttpRequestHeaders headers, Dictionary reqHeaders = null) { + // 额外 headers + if (reqHeaders != null) { + foreach (KeyValuePair kv in reqHeaders) { + headers.Add(kv.Key, kv.Value.ToString()); + } + } + if (additionalHeaders.Count > 0) { + foreach (KeyValuePair kv in additionalHeaders) { + headers.Add(kv.Key, kv.Value); + } + } + // 服务额外 headers + foreach (KeyValuePair>> kv in runtimeHeaderTasks) { + if (headers.Contains(kv.Key)) { + continue; + } + string value = await kv.Value.Invoke(); + if (value == null) { + continue; + } + headers.Add(kv.Key, value); + } + } + } +} diff --git a/Runtime/Internal/Http/TapHttpClient.cs.meta b/Runtime/Internal/Http/TapHttpClient.cs.meta new file mode 100644 index 0000000..73d5c52 --- /dev/null +++ b/Runtime/Internal/Http/TapHttpClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7886abbfa16f547209a283a98e0b2895 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Http/TapHttpUtils.cs b/Runtime/Internal/Http/TapHttpUtils.cs new file mode 100644 index 0000000..fea727c --- /dev/null +++ b/Runtime/Internal/Http/TapHttpUtils.cs @@ -0,0 +1,83 @@ +using System.Linq; +using System.Text; +using System.Collections.Specialized; +using System.Net.Http; +using UnityEngine; + +namespace TapSDK.Core.Internal.Http { + public class TapHttpUtils { + public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) { + if (TapLogger.LogDelegate == null) { + return; + } + if (client == null) { + return; + } + if (request == null) { + return; + } + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=== HTTP Request Start ==="); + sb.AppendLine($"URL: {request.RequestUri}"); + sb.AppendLine($"Method: {request.Method}"); + sb.AppendLine($"Headers: "); + foreach (var header in client.DefaultRequestHeaders) { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + foreach (var header in request.Headers) { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + if (request.Content != null) { + foreach (var header in request.Content.Headers) { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + } + if (!string.IsNullOrEmpty(content)) { + sb.AppendLine($"Content: {content}"); + } + sb.AppendLine("=== HTTP Request End ==="); + Debug.Log(sb.ToString()); + } + + public static void PrintResponse(HttpResponseMessage response, string content = null) { + if (TapLogger.LogDelegate == null) { + return; + } + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=== HTTP Response Start ==="); + sb.AppendLine($"URL: {response.RequestMessage.RequestUri}"); + sb.AppendLine($"Status Code: {response.StatusCode}"); + if (!string.IsNullOrEmpty(content)) { + sb.AppendLine($"Content: {content}"); + } + sb.AppendLine("=== HTTP Response End ==="); + Debug.Log(sb.ToString()); + } + + public static NameValueCollection ParseQueryString(string queryString) + { + if (string.IsNullOrEmpty(queryString)) { + return null; + } + + if (queryString.Length > 0 && queryString[0] == '?') { + queryString = queryString.Substring(1); + } + NameValueCollection queryParameters = new NameValueCollection(); + string[] querySegments = queryString.Split('&'); + foreach(string segment in querySegments) + { + string[] parts = segment.Split('='); + if (parts.Length > 0) + { + string key = parts[0].Trim(new char[] { '?', ' ' }); + string val = parts[1].Trim(); + + queryParameters.Add(key, val); + } + } + + return queryParameters; + } + } +} diff --git a/Runtime/Internal/Http/TapHttpUtils.cs.meta b/Runtime/Internal/Http/TapHttpUtils.cs.meta new file mode 100644 index 0000000..6b4d2b5 --- /dev/null +++ b/Runtime/Internal/Http/TapHttpUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c16aefb23bd7494d83c246505380cab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Init.meta b/Runtime/Internal/Init.meta new file mode 100644 index 0000000..b513da6 --- /dev/null +++ b/Runtime/Internal/Init.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d8e48cb41106433ba4f262639977f77 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Init/IInitTask.cs b/Runtime/Internal/Init/IInitTask.cs new file mode 100644 index 0000000..9721e34 --- /dev/null +++ b/Runtime/Internal/Init/IInitTask.cs @@ -0,0 +1,18 @@ + +namespace TapSDK.Core.Internal.Init { + public interface IInitTask { + /// + /// 初始化顺序 + /// + int Order { get; } + + /// + /// 初始化 + /// + /// + void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions); + + void Init(TapTapSdkOptions coreOption); + + } +} diff --git a/Runtime/Internal/Init/IInitTask.cs.meta b/Runtime/Internal/Init/IInitTask.cs.meta new file mode 100644 index 0000000..8adbe72 --- /dev/null +++ b/Runtime/Internal/Init/IInitTask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d28a8b38a6fd48249e82ff24e528080 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Json.meta b/Runtime/Internal/Json.meta new file mode 100644 index 0000000..c3e7ccf --- /dev/null +++ b/Runtime/Internal/Json.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 91e47674c73b246fdbda4fb286209bd3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Json/TapJsonConverter.cs b/Runtime/Internal/Json/TapJsonConverter.cs new file mode 100644 index 0000000..44ce8a7 --- /dev/null +++ b/Runtime/Internal/Json/TapJsonConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TapSDK.Core.Internal.Json { + public class TapJsonConverter : JsonConverter { + public override bool CanConvert(Type objectType) { + return objectType == typeof(object); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + serializer.Serialize(writer, value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + if (reader.TokenType == JsonToken.StartObject) { + var obj = new Dictionary(); + serializer.Populate(reader, obj); + return obj; + } + if (reader.TokenType == JsonToken.StartArray) { + var arr = new List(); + serializer.Populate(reader, arr); + return arr; + } + if (reader.TokenType == JsonToken.Integer) { + if ((long)reader.Value < int.MaxValue) { + return Convert.ToInt32(reader.Value); + } + } + if (reader.TokenType == JsonToken.Float) { + return Convert.ToSingle(reader.Value); + } + + return serializer.Deserialize(reader); + } + + public readonly static TapJsonConverter Default = new TapJsonConverter(); + } +} diff --git a/Runtime/Internal/Json/TapJsonConverter.cs.meta b/Runtime/Internal/Json/TapJsonConverter.cs.meta new file mode 100644 index 0000000..a41fc21 --- /dev/null +++ b/Runtime/Internal/Json/TapJsonConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e7910a9ac70c45509b1520d9d614ce0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Log.meta b/Runtime/Internal/Log.meta new file mode 100644 index 0000000..ecfbbb3 --- /dev/null +++ b/Runtime/Internal/Log.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee253dc23c74a430e91bd9791fb9043d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Log/TapLog.cs b/Runtime/Internal/Log/TapLog.cs new file mode 100644 index 0000000..0340f37 --- /dev/null +++ b/Runtime/Internal/Log/TapLog.cs @@ -0,0 +1,117 @@ +using UnityEngine; + +namespace TapSDK.Core.Internal.Log +{ + public class TapLog + { + private const string TAG = "TapSDK"; + // 颜色常量 + private const string InfoColor = "#FFFFFF"; // 白色 + private const string WarningColor = "#FFFF00"; // 黄色 + private const string ErrorColor = "#FF0000"; // 红色 + private const string MainThreadColor = "#00FF00"; // 绿色 + private const string IOThreadColor = "#FF00FF"; // 紫色 + private const string TagColor = "#00FFFF"; // 青色 + + // 开关变量,控制是否启用日志输出 + public static bool Enabled = false; + + private string module; + private string tag; + + public TapLog(string module, string tag = TAG) + { + this.tag = tag; + this.module = module; + } + + public void Log(string message, string detail = null) + { + TapLog.Log(message, detail, tag, module); + } + + // 输出带有自定义颜色和标签的警告 + public void Warning(string message, string detail = null) + { + TapLog.Warning(message, detail, tag, module); + } + + // 输出带有自定义颜色和标签的错误 + public void Error(string message, string detail = null) + { + TapLog.Error(message, detail, tag, module); + } + + // 输出带有自定义颜色和标签的普通日志 + public static void Log(string message, string detail = null, string tag = TAG, string module = null) + { + if (Enabled) + { + Debug.Log(GetFormattedMessage(message: message, detail: detail, colorHex: InfoColor, tag: tag, module: module)); + } + } + + // 输出带有自定义颜色和标签的警告 + public static void Warning(string message, string detail = null, string tag = TAG, string module = null) + { + if (Enabled) + { + Debug.LogWarning(GetFormattedMessage(message: message, detail: detail, colorHex: WarningColor, tag: tag, module: module)); + } + } + + // 输出带有自定义颜色和标签的错误 + public static void Error(string message, string detail = null, string tag = TAG, string module = null) + { + if (Enabled) + { + Debug.LogError(GetFormattedMessage(message: message, detail: detail, colorHex: ErrorColor, tag: tag, module: module)); + } + } + + // 格式化带有颜色和标签的消息 + private static string GetFormattedMessage(string message, string detail, string colorHex, string tag, string module) + { + string threadInfo = GetThreadInfo(); + string tagColor = TagColor; + if (module != null && module != "") + { + tag = $"{tag}.{module}"; + } + if (IsMobilePlatform()) + { + return $"[{tag}] {threadInfo} {message}\n{detail}"; + } + else + { + return $"[{tag}] {threadInfo} {message}\n{detail}\n"; + } + + } + + // 获取当前线程信息 + private static string GetThreadInfo() + { + bool isMainThread = System.Threading.Thread.CurrentThread.IsAlive && System.Threading.Thread.CurrentThread.ManagedThreadId == 1; + string threadInfo = isMainThread ? "Main Thread" : $"IO Thread {System.Threading.Thread.CurrentThread.ManagedThreadId}"; + + if (IsMobilePlatform()) + { + // 移动平台的线程信息不使用颜色 + return $"({threadInfo})"; + } + else + { + // 其他平台的线程信息使用颜色 + string color = isMainThread ? MainThreadColor : IOThreadColor; + return $"({threadInfo})"; + } + } + + // 检查是否是移动平台 + private static bool IsMobilePlatform() + { + return Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer; + } + } +} diff --git a/Runtime/Internal/Log/TapLog.cs.meta b/Runtime/Internal/Log/TapLog.cs.meta new file mode 100644 index 0000000..e25cf48 --- /dev/null +++ b/Runtime/Internal/Log/TapLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9613fc51bb6646d89b77a0fde4d4b40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Platform.meta b/Runtime/Internal/Platform.meta new file mode 100644 index 0000000..fe1502a --- /dev/null +++ b/Runtime/Internal/Platform.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93f336f19f5594d78bf1cd3f6ca3e876 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Platform/ITapCorePlatform.cs b/Runtime/Internal/Platform/ITapCorePlatform.cs new file mode 100644 index 0000000..22c43b2 --- /dev/null +++ b/Runtime/Internal/Platform/ITapCorePlatform.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace TapSDK.Core.Internal { + public interface ITapCorePlatform { + void Init(TapTapSdkOptions config); + + void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions); + + void UpdateLanguage(TapTapLanguageType language); + } +} \ No newline at end of file diff --git a/Runtime/Internal/Platform/ITapCorePlatform.cs.meta b/Runtime/Internal/Platform/ITapCorePlatform.cs.meta new file mode 100644 index 0000000..1dac583 --- /dev/null +++ b/Runtime/Internal/Platform/ITapCorePlatform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 693f034729dd341468d896eecf035ed2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Platform/ITapEventPlatform.cs b/Runtime/Internal/Platform/ITapEventPlatform.cs new file mode 100644 index 0000000..60dfaa4 --- /dev/null +++ b/Runtime/Internal/Platform/ITapEventPlatform.cs @@ -0,0 +1,37 @@ +using System; + +namespace TapSDK.Core.Internal { + public interface ITapEventPlatform { + void SetUserID(string userID); + + void SetUserID(string userID, string properties); + void ClearUser(); + + string GetDeviceId(); + + void LogEvent(string name, string properties); + + void DeviceInitialize(string properties); + + void DeviceUpdate(string properties); + void DeviceAdd(string properties); + + void UserInitialize(string properties); + + void UserUpdate(string properties); + + void UserAdd(string properties); + + void AddCommonProperty(string key, string value); + + void AddCommon(string properties); + + void ClearCommonProperty(string key); + void ClearCommonProperties(string[] keys); + + void ClearAllCommonProperties(); + void LogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties); + + void RegisterDynamicProperties(Func callback); + } +} \ No newline at end of file diff --git a/Runtime/Internal/Platform/ITapEventPlatform.cs.meta b/Runtime/Internal/Platform/ITapEventPlatform.cs.meta new file mode 100644 index 0000000..8e465c1 --- /dev/null +++ b/Runtime/Internal/Platform/ITapEventPlatform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b1164922a79c4217bc3f9dc0cac0ff3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Platform/PlatformTypeUtils.cs b/Runtime/Internal/Platform/PlatformTypeUtils.cs new file mode 100644 index 0000000..2625300 --- /dev/null +++ b/Runtime/Internal/Platform/PlatformTypeUtils.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace TapSDK.Core.Internal { + public static class PlatformTypeUtils { + /// + /// 创建平台接口实现类对象 + /// + /// + /// + /// + public static object CreatePlatformImplementationObject(Type interfaceType, string startWith) { + // Debug.Log($"Searching for types in assemblies starting with: {startWith} that implement: {interfaceType}"); + + // 获取所有符合条件的程序集 + var assemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(assembly => assembly.GetName().FullName.StartsWith(startWith)); + + // foreach (var assembly in assemblies) { + // Debug.Log($"Found assembly: {assembly.GetName().FullName}"); + // } + + // 获取符合条件的类型 + Type platformSupportType = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .SingleOrDefault(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass); + + if (platformSupportType != null) { + // Debug.Log($"Found type: {platformSupportType.FullName}, creating instance..."); + try + { + return Activator.CreateInstance(platformSupportType); + } + catch (Exception ex) + { + Debug.LogError($"Failed to create instance of {platformSupportType.FullName}: {ex}"); + } + } else { + Debug.LogError($"No type found that implements {interfaceType} in assemblies starting with {startWith}"); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/Platform/PlatformTypeUtils.cs.meta b/Runtime/Internal/Platform/PlatformTypeUtils.cs.meta new file mode 100644 index 0000000..25220e0 --- /dev/null +++ b/Runtime/Internal/Platform/PlatformTypeUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22c79a001ad8e4956a7af00198f39c33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI.meta b/Runtime/Internal/UI.meta new file mode 100644 index 0000000..1978a31 --- /dev/null +++ b/Runtime/Internal/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd5e6d0a0a3084dbe9172f6644b9afee +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base.meta b/Runtime/Internal/UI/Base.meta new file mode 100644 index 0000000..9a538aa --- /dev/null +++ b/Runtime/Internal/UI/Base.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 02106d3e1676c4ae283349aad59eaf9d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/Const.cs b/Runtime/Internal/UI/Base/Const.cs new file mode 100644 index 0000000..f36149a --- /dev/null +++ b/Runtime/Internal/UI/Base/Const.cs @@ -0,0 +1,27 @@ +using System; +namespace TapSDK.UI +{ + public enum EOpenMode + { + Sync, + + Async, + } + + [Flags] + public enum EAnimationMode + { + None = 0, + + Alpha = 1 << 0, + + Scale = 1 << 1, + + RightSlideIn = 1 << 2, + + UpSlideIn = 1 << 3, + ToastSlideIn = 1 << 4, + + AlphaAndScale = Alpha | Scale, + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/Const.cs.meta b/Runtime/Internal/UI/Base/Const.cs.meta new file mode 100644 index 0000000..4e33df5 --- /dev/null +++ b/Runtime/Internal/UI/Base/Const.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bd4da1dce99741fca662c311f199c2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs b/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs new file mode 100644 index 0000000..e44c9cd --- /dev/null +++ b/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.Serialization; +using UnityEngine.UI; + +namespace TapTap.UI +{ + /// + /// 修复UnityUGui中Canvas画布在Windows平台宽高比过长(横屏拉很长)导致UI的最右边一部分无法被点击的Bug,现发现与2021.3.x版本 + /// + public class GraphicRaycasterBugFixed : GraphicRaycaster + { + [NonSerialized] private List m_RaycastResults = new List(); + private Canvas _Canvas; + private Canvas canvasBugFixed + { + get + { + if (_Canvas != null) + return _Canvas; + + _Canvas = GetComponent(); + return _Canvas; + } + } + /// + /// Perform the raycast against the list of graphics associated with the Canvas. + /// + /// Current event data + /// List of hit objects to append new results to. + public override void Raycast(PointerEventData eventData, List resultAppendList) + { + if (canvasBugFixed == null) + return; + + var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvasBugFixed); + if (canvasGraphics == null || canvasGraphics.Count == 0) + return; + + int displayIndex; + var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference + + if (canvasBugFixed.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null) + displayIndex = canvasBugFixed.targetDisplay; + else + displayIndex = currentEventCamera.targetDisplay; + + //!! 不同之处 + var eventPosition = Display.RelativeMouseAt(eventData.position); + //MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position); + if (eventPosition != Vector3.zero) + { + // We support multiple display and display identification based on event position. + + int eventDisplayIndex = (int)eventPosition.z; + + // Discard events that are not part of this display so the user does not interact with multiple displays at once. + if (eventDisplayIndex != displayIndex) + return; + } + else + { + // The multiple display system is not supported on all platforms, when it is not supported the returned position + // will be all zeros so when the returned index is 0 we will default to the event data to be safe. + eventPosition = eventData.position; + + // We dont really know in which display the event occured. We will process the event assuming it occured in our display. + } + + // Convert to view space + Vector2 pos; + if (currentEventCamera == null) + { + // Multiple display support only when not the main display. For display 0 the reported + // resolution is always the desktops resolution since its part of the display API, + // so we use the standard none multiple display method. (case 741751) + float w = Screen.width; + float h = Screen.height; + if (displayIndex > 0 && displayIndex < Display.displays.Length) + { + w = Display.displays[displayIndex].systemWidth; + h = Display.displays[displayIndex].systemHeight; + } + pos = new Vector2(eventPosition.x / w, eventPosition.y / h); + } + else + pos = currentEventCamera.ScreenToViewportPoint(eventPosition); + + // If it's outside the camera's viewport, do nothing + if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) + return; + + float hitDistance = float.MaxValue; + + Ray ray = new Ray(); + + if (currentEventCamera != null) + ray = currentEventCamera.ScreenPointToRay(eventPosition); + + if (canvasBugFixed.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None) + { + float distanceToClipPlane = 100.0f; + + if (currentEventCamera != null) + { + float projectionDirection = ray.direction.z; + distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection) + ? Mathf.Infinity + : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection); + } + #if PACKAGE_PHYSICS + if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All) + { + if (ReflectionMethodsCache.Singleton.raycast3D != null) + { + var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask); + if (hits.Length > 0) + hitDistance = hits[0].distance; + } + } + #endif + #if PACKAGE_PHYSICS2D + if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All) + { + if (ReflectionMethodsCache.Singleton.raycast2D != null) + { + var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask); + if (hits.Length > 0) + hitDistance = hits[0].distance; + } + } + #endif + } + + m_RaycastResults.Clear(); + + Raycast(canvasBugFixed, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults); + + int totalCount = m_RaycastResults.Count; + for (var index = 0; index < totalCount; index++) + { + var go = m_RaycastResults[index].gameObject; + bool appendGraphic = true; + + if (ignoreReversedGraphics) + { + if (currentEventCamera == null) + { + // If we dont have a camera we know that we should always be facing forward + var dir = go.transform.rotation * Vector3.forward; + appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0; + } + else + { + // If we have a camera compare the direction against the cameras forward. + var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane; + // !!不同之处 + appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0; + } + } + + if (appendGraphic) + { + float distance = 0; + Transform trans = go.transform; + Vector3 transForward = trans.forward; + + if (currentEventCamera == null || canvasBugFixed.renderMode == RenderMode.ScreenSpaceOverlay) + distance = 0; + else + { + // http://geomalgorithms.com/a06-_intersect-2.html + distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction)); + + // Check to see if the go is behind the camera. + if (distance < 0) + continue; + } + + if (distance >= hitDistance) + continue; + + var castResult = new RaycastResult + { + gameObject = go, + module = this, + distance = distance, + screenPosition = eventPosition, + displayIndex = displayIndex, + index = resultAppendList.Count, + depth = m_RaycastResults[index].depth, + sortingLayer = canvasBugFixed.sortingLayerID, + sortingOrder = canvasBugFixed.sortingOrder, + worldPosition = ray.origin + ray.direction * distance, + worldNormal = -transForward + }; + resultAppendList.Add(castResult); + } + } + } + /// + /// Perform a raycast into the screen and collect all graphics underneath it. + /// + [NonSerialized] static readonly List s_SortedGraphics = new List(); + private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList foundGraphics, List results) + { + // Necessary for the event system + int totalCount = foundGraphics.Count; + for (int i = 0; i < totalCount; ++i) + { + Graphic graphic = foundGraphics[i]; + + // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn + if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1) + continue; + + if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera)) + continue; + + if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane) + continue; + + if (graphic.Raycast(pointerPosition, eventCamera)) + { + s_SortedGraphics.Add(graphic); + } + } + + s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth)); + totalCount = s_SortedGraphics.Count; + for (int i = 0; i < totalCount; ++i) + results.Add(s_SortedGraphics[i]); + + s_SortedGraphics.Clear(); + } + } +} diff --git a/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs.meta b/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs.meta new file mode 100644 index 0000000..b281556 --- /dev/null +++ b/Runtime/Internal/UI/Base/GraphicRaycasterBugFixed.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 39341cb1058fe4bdebd02bc59f5c08e1 +timeCreated: 1678941027 \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/LoadingPanelController.cs b/Runtime/Internal/UI/Base/LoadingPanelController.cs new file mode 100644 index 0000000..715bcd6 --- /dev/null +++ b/Runtime/Internal/UI/Base/LoadingPanelController.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +namespace TapSDK.UI +{ + public class LoadingPanelController : BasePanelController + { + public Transform rotater; + + public float speed = 10; + /// + /// bind ugui components for every panel + /// + protected override void BindComponents() + { + rotater = transform.Find("Image");; + } + + private void Update() + { + if (rotater != null) + { + var localEuler = rotater.localEulerAngles; + var z = rotater.localEulerAngles.z; + z += Time.deltaTime * speed; + rotater.localEulerAngles = new Vector3(localEuler.x, localEuler.y, z); + } + } + } +} diff --git a/Runtime/Internal/UI/Base/LoadingPanelController.cs.meta b/Runtime/Internal/UI/Base/LoadingPanelController.cs.meta new file mode 100644 index 0000000..0e373ce --- /dev/null +++ b/Runtime/Internal/UI/Base/LoadingPanelController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 270dfb0d584134275afb9640c5abde2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/MonoSingleton.cs b/Runtime/Internal/UI/Base/MonoSingleton.cs new file mode 100644 index 0000000..9625321 --- /dev/null +++ b/Runtime/Internal/UI/Base/MonoSingleton.cs @@ -0,0 +1,102 @@ +using System; +using UnityEngine; + +namespace TapSDK.UI +{ + public class MonoSingleton : MonoBehaviour where T : Component + { + private static T _instance; + + public static T Instance + { + get + { + if (_instance == null && Application.isPlaying) + { + CreateInstance(); + } + + return _instance; + } + } + + private static void CreateInstance() + { + Type theType = typeof(T); + + _instance = (T)FindObjectOfType(theType); + + if (_instance == null) + { + var go = new GameObject(typeof(T).Name); + _instance = go.AddComponent(); + + GameObject rootObj = GameObject.Find("TapSDKSingletons"); + if (rootObj == null) + { + rootObj = new GameObject("TapSDKSingletons"); + DontDestroyOnLoad(rootObj); + } + + go.transform.SetParent(rootObj.transform); + } + } + + public static void DestroyInstance() + { + if (_instance != null) + { + Destroy(_instance.gameObject); + } + } + + public static bool HasInstance() + { + return _instance != null; + } + + protected virtual void Awake() + { + if (_instance != null && _instance.gameObject != gameObject) + { + if (Application.isPlaying) + { + Destroy(gameObject); + } + else + { + DestroyImmediate(gameObject); // UNITY_EDITOR + } + + return; + } + else if (_instance == null) + { + _instance = GetComponent(); + } + + DontDestroyOnLoad(gameObject); + + Init(); + } + + protected virtual void OnDestroy() + { + Uninit(); + + if (_instance != null && _instance.gameObject == gameObject) + { + _instance = null; + } + } + + public virtual void Init() + { + } + + public virtual void Uninit() + { + } + + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/MonoSingleton.cs.meta b/Runtime/Internal/UI/Base/MonoSingleton.cs.meta new file mode 100644 index 0000000..125a246 --- /dev/null +++ b/Runtime/Internal/UI/Base/MonoSingleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2743c376f0b344d32a58d36d253b50ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/Singleton.cs b/Runtime/Internal/UI/Base/Singleton.cs new file mode 100644 index 0000000..966e0b0 --- /dev/null +++ b/Runtime/Internal/UI/Base/Singleton.cs @@ -0,0 +1,29 @@ +using System; + +namespace TapSDK.UI +{ + public class Singleton where T : class + { + private static T _instance; + private static readonly object _lock = new object(); + + public static T Instance + { + get + { + if (_instance == null) + { + lock (_lock) + { + if (_instance == null) + { + _instance = (T)Activator.CreateInstance(typeof(T), true); + } + } + } + + return _instance; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/Singleton.cs.meta b/Runtime/Internal/UI/Base/Singleton.cs.meta new file mode 100644 index 0000000..58c4128 --- /dev/null +++ b/Runtime/Internal/UI/Base/Singleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 641507a330f89486c951299818e8d14e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/TipPanelController.cs b/Runtime/Internal/UI/Base/TipPanelController.cs new file mode 100644 index 0000000..c274b01 --- /dev/null +++ b/Runtime/Internal/UI/Base/TipPanelController.cs @@ -0,0 +1,45 @@ +using System; +using UnityEngine; +using UnityEngine.UI; + +namespace TapSDK.UI +{ + public class TipPanelOpenParam : AbstractOpenPanelParameter + { + public string text; + + public Color textColor; + + public TextAnchor textAnchor; + + public TipPanelOpenParam(string text, Color textColor, TextAnchor textAnchor) + { + this.text = text; + this.textColor = textColor; + this.textAnchor = textAnchor; + } + } + public class TipPanelController : BasePanelController + { + public Text text; + + protected override void BindComponents() + { + base.BindComponents(); + text = transform.Find("Text").GetComponent(); + } + + protected override void OnLoadSuccess() + { + base.OnLoadSuccess(); + TipPanelOpenParam param = this.openParam as TipPanelOpenParam; + if (param != null) + { + text.text = param.text; + text.color = param.textColor; + text.alignment = param.textAnchor; + } + } + + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/TipPanelController.cs.meta b/Runtime/Internal/UI/Base/TipPanelController.cs.meta new file mode 100644 index 0000000..ac17211 --- /dev/null +++ b/Runtime/Internal/UI/Base/TipPanelController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4bf8716943aca4601b13dfac46ccf44b +timeCreated: 1690867588 \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/ToastBlackPanelController.cs b/Runtime/Internal/UI/Base/ToastBlackPanelController.cs new file mode 100644 index 0000000..3d27791 --- /dev/null +++ b/Runtime/Internal/UI/Base/ToastBlackPanelController.cs @@ -0,0 +1,95 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace TapSDK.UI { + public class ToastPanelOpenParam : AbstractOpenPanelParameter { + public float popupTime; + + public string text; + + public Texture icon; + + public ToastPanelOpenParam(string text, float popupTime, Texture icon = null) { + this.text = text; + this.popupTime = popupTime; + this.icon = icon; + } + } + + public class ToastBlackPanelController : BasePanelController { + private const int MAX_CHAR_COUNT = 87; + private const float MIN_HORIZONTAL_LENGTH = 210; + private const float MAX_HORIZONTAL_LENGTH = 1252; + [HideInInspector] + public Text text; + [HideInInspector] + public RectTransform background; + [HideInInspector] + public LayoutGroup layout; + [HideInInspector] + public RawImage iconImage; + + public float fixVal; + + public float animationTime; + + [SerializeField] + private float sizeDeltaX = 50f; + + protected override void BindComponents() { + base.BindComponents(); + background = transform.Find("Root/BGM") as RectTransform; + layout = background.transform.Find("Container").GetComponent(); + text = layout.transform.Find("Text").GetComponent(); + iconImage = layout.transform.Find("Mask/Icon").GetComponent(); + fadeAnimationTime = animationTime; + } + + protected override void OnLoadSuccess() { + base.OnLoadSuccess(); + ToastPanelOpenParam param = this.openParam as ToastPanelOpenParam; + if (param != null) { + + iconImage.gameObject.SetActive(param.icon != null); + layout.enabled = param.icon != null; + text.text = param.text; + bool overflow; + // test: 已登录账号:海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边边... + var totalLength = CalculateLengthOfText(out overflow); + if (overflow) { + bool haveIcon = param.icon != null; + int length = Mathf.Min(MAX_CHAR_COUNT - (haveIcon ? 4 : 0), param.text.Length - 1); + var transformedText = param.text.Substring(0, length) + "..."; + text.text = transformedText; + } + var x = totalLength; + var y = background.sizeDelta.y; + background.sizeDelta = new Vector2(x, y); + var textRect = text.transform as RectTransform; + + if (param.icon != null) { + iconImage.texture = param.icon; + text.alignment = TextAnchor.MiddleLeft; + textRect.sizeDelta = new Vector2(x - sizeDeltaX, 24); + } + else { + text.alignment = TextAnchor.MiddleCenter; + textRect.anchoredPosition = Vector2.zero; + textRect.anchorMin = Vector2.zero; + textRect.anchorMax = Vector2.one; + textRect.sizeDelta = Vector2.zero; + } + this.Invoke("Close", param.popupTime); + } + } + + protected float CalculateLengthOfText( out bool horizontalOverflow) { + var width = text.preferredWidth + fixVal + 12; + width = Mathf.Max(MIN_HORIZONTAL_LENGTH, width); + var maxWidth = MAX_HORIZONTAL_LENGTH; + horizontalOverflow = width > maxWidth; + width = Mathf.Min(maxWidth, width); + return width; + } + } +} diff --git a/Runtime/Internal/UI/Base/ToastBlackPanelController.cs.meta b/Runtime/Internal/UI/Base/ToastBlackPanelController.cs.meta new file mode 100644 index 0000000..b3b8b08 --- /dev/null +++ b/Runtime/Internal/UI/Base/ToastBlackPanelController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cfadb0c2f9cbd4e26821f6acbffb6dac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Base/ToastWhitePanelController.cs b/Runtime/Internal/UI/Base/ToastWhitePanelController.cs new file mode 100644 index 0000000..6b56f88 --- /dev/null +++ b/Runtime/Internal/UI/Base/ToastWhitePanelController.cs @@ -0,0 +1,79 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace TapSDK.UI { + public class ToastWhitePanelController : BasePanelController { + private const int MAX_CHAR_COUNT = 69; + private const float MIN_HORIZONTAL_LENGTH = 120; + private const float MAX_HORIZONTAL_LENGTH = 1252; + private const float BACKGROUND_WIDTH_HELPER = 60; + [HideInInspector] + public Text text; + [HideInInspector] + public RectTransform background; + [HideInInspector] + public RawImage iconImage; + + public float fixVal; + + public float animationTime; + + public ToastPanelOpenParam toastParam; + + protected override void BindComponents() { + base.BindComponents(); + background = transform.Find("Root/BGM") as RectTransform; + text = background.transform.Find("Container/Text").GetComponent(); + iconImage = background.transform.Find("Container/Image").GetComponent(); + fadeAnimationTime = animationTime; + } + + protected override void OnLoadSuccess() { + base.OnLoadSuccess(); + ToastPanelOpenParam param = this.openParam as ToastPanelOpenParam; + toastParam = param; + if (param != null) { + iconImage.gameObject.SetActive(param.icon != null); + text.text = param.text; + bool overflow = text.text.Length > MAX_CHAR_COUNT; + // test: 已登录账号:海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边晒太阳海绵宝宝和派大星一起在海边边... + var totalLength = CalculateLengthOfText(); + if (overflow) { + bool haveIcon = param.icon != null; + int length = Mathf.Min(MAX_CHAR_COUNT - (haveIcon ? 4 : 0), param.text.Length - 1); + var transformedText = param.text.Substring(0, length) + "..."; + text.text = transformedText; + } + var x = totalLength; + var y = background.sizeDelta.y; + background.sizeDelta = new Vector2(x + BACKGROUND_WIDTH_HELPER, y); + var textRect = text.transform as RectTransform; + + if (param.icon != null) { + iconImage.texture = param.icon; + textRect.sizeDelta = new Vector2(x, y); + } + else { + textRect.anchoredPosition = Vector2.zero; + textRect.anchorMin = Vector2.zero; + textRect.anchorMax = Vector2.one; + textRect.sizeDelta = Vector2.zero; + } + this.Invoke("Close", param.popupTime); + } + } + + private float CalculateLengthOfText() { + var width = text.preferredWidth + fixVal; + width = Mathf.Max(MIN_HORIZONTAL_LENGTH, width); + var maxWidth = MAX_HORIZONTAL_LENGTH; + width = Mathf.Min(maxWidth, width); + return width; + } + + public void Refresh(float refreshTime) { + this.CancelInvoke("Close"); + this.Invoke("Close", refreshTime); + } + } +} diff --git a/Runtime/Internal/UI/Base/ToastWhitePanelController.cs.meta b/Runtime/Internal/UI/Base/ToastWhitePanelController.cs.meta new file mode 100644 index 0000000..898e6cc --- /dev/null +++ b/Runtime/Internal/UI/Base/ToastWhitePanelController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5e2bbf05d8861431bb645d167b1ab76c +timeCreated: 1695367197 \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/UIManager.cs b/Runtime/Internal/UI/Base/UIManager.cs new file mode 100644 index 0000000..accd406 --- /dev/null +++ b/Runtime/Internal/UI/Base/UIManager.cs @@ -0,0 +1,724 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; + +namespace TapSDK.UI +{ + public class UIManager : MonoSingleton + { + public enum GeneralToastLevel { + None = 0, + Info = 1, + Warning = 2, + Error = 3, + Success =4, + } + + private class ToastConfig { + public bool white; + public string text; + public float popupTime; + public Texture icon; + } + + private static Texture whiteToastSuccessIcon; + private static Texture whiteToastErrorIcon; + private static Texture whiteToastInfoIcon; + private static Texture whiteToastWarningIcon; + private static Texture taptapToastIcon; + + public static Texture WhiteToastSuccessIcon { + get { + if (whiteToastSuccessIcon == null) + whiteToastSuccessIcon = UnityEngine.Resources.Load("taptap-toast-success"); + return whiteToastSuccessIcon; + } + } + + public static Texture WhiteToastErrorIcon { + get { + if (whiteToastErrorIcon == null) + whiteToastErrorIcon = UnityEngine.Resources.Load("taptap-toast-error"); + return whiteToastErrorIcon; + } + } + + public static Texture WhiteToastWarningIcon { + get { + if (whiteToastWarningIcon == null) + whiteToastWarningIcon = UnityEngine.Resources.Load("taptap-toast-warning"); + return whiteToastWarningIcon; + } + } + + public static Texture WhiteToastInfoIcon { + get { + if (whiteToastInfoIcon == null) + whiteToastInfoIcon = UnityEngine.Resources.Load("taptap-toast-info"); + return whiteToastInfoIcon; + } + } + + public static Texture TapTapToastIcon { + get { + if (taptapToastIcon == null) + taptapToastIcon = Resources.Load("TapSDKCommonTapIcon-v2"); + return taptapToastIcon; + } + } + + private List _toastCacheData = new List(12); + public const int TOPPEST_SORTING_ORDER = 32767; + + private const int LOADING_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER; + + private const int TOAST_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER - 10; + private const int TIP_PANEL_SORTING_ORDER = TOPPEST_SORTING_ORDER - 20; + + private const int REFERENCE_WIDTH = 1920; + private const int REFERENCE_HEIGHT = 1080; + + private readonly Dictionary _registerPanels = new Dictionary(); + + // uicamera + private Camera _uiCamera; + + private GameObject _uiRoot; + private GameObject _uiConstantRoot; + private Canvas _uiRootCanvas; + + public Camera UiCamera => _uiCamera; + + public override void Init() + { + if (_uiRoot == null && Application.isPlaying) + { + CreateUIRoot(); + } + } + + private void CreateUIRoot() + { + _uiRoot = Instantiate(Resources.Load("TapSDKUIRoot")); + DontDestroyOnLoad(_uiRoot); + var canvas = _uiRoot.GetComponent(); + _uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? Camera.main : canvas.worldCamera; + _uiRoot.transform.SetParent(UIManager.Instance.transform); + _uiRootCanvas = _uiRoot.transform.GetComponent(); + + CanvasScaler scaler = _uiRoot.GetComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; + scaler.referenceResolution = new Vector2(REFERENCE_WIDTH, REFERENCE_HEIGHT); + float referenceRatio = REFERENCE_WIDTH * 1.0f / REFERENCE_HEIGHT; + float screenRatio = Screen.width * 1.0f / Screen.height; + scaler.matchWidthOrHeight = screenRatio > referenceRatio ? 1 : 0; + + _uiConstantRoot = Instantiate(Resources.Load("TapSDKConstantUIRoot")); + DontDestroyOnLoad(_uiConstantRoot); + canvas = _uiConstantRoot.GetComponent(); + _uiCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? Camera.main : canvas.worldCamera; + _uiConstantRoot.transform.SetParent(UIManager.Instance.transform); + _uiRootCanvas = _uiConstantRoot.transform.GetComponent(); + } + + public override void Uninit() + { + _uiCamera = null; + _uiRoot = null; + _uiConstantRoot = null; + _uiRootCanvas = null; + base.Uninit(); + } + + /// + /// Get Or Create UI asynchronously + /// + /// the prefab path + /// opening param + /// if u want to get ui do something,here is for u, which is invoked after BasePanelController.OnLoadSuccess + /// + /// get panel instance if sync mode load + public async Task OpenUIAsync(string path, AbstractOpenPanelParameter param = null, Action onAsyncGet = null) where TPanelController : BasePanelController + { + var basePanelController = GetUI(); + + // 如果已有界面(之前缓存过的),则不执行任何操作 + if (basePanelController != null) + { + if (!basePanelController.canvas.enabled) + { + basePanelController.canvas.enabled = true; + } + + onAsyncGet?.Invoke(basePanelController); + + return basePanelController; + } + + ResourceRequest request = Resources.LoadAsync(path); + while (request.isDone == false) + { + await Task.Yield(); + } + + GameObject go = request.asset as GameObject; + var basePanel = Instantiate(go).GetComponent(); + if (basePanel != null) + { + var prefabRectTransform = basePanel.transform as RectTransform; + var anchorMin = prefabRectTransform.anchorMin; + var anchorMax = prefabRectTransform.anchorMax; + var anchoredPosition = prefabRectTransform.anchoredPosition; + var sizeDelta = prefabRectTransform.sizeDelta; + + InternalOnPanelLoaded(typeof(TPanelController), basePanel, param); + if (IsNeedCorrectRectTransform(basePanel)) + { + var panelRectTransform = basePanel.transform as RectTransform; + panelRectTransform.anchorMin = anchorMin; + panelRectTransform.anchorMax = anchorMax; + panelRectTransform.anchoredPosition = anchoredPosition; + panelRectTransform.sizeDelta = sizeDelta; + } + onAsyncGet?.Invoke(basePanel); + + EnsureSpecialPanel(); + return basePanel; + } + else + { + return null; + } + } + + /// + /// Get Or Create UI asynchronously + /// + /// the panel Type + /// the prefab path + /// opening param + /// if u want to get ui do something,here is for u, which is invoked after BasePanelController.OnLoadSuccess + /// get panel instance if sync mode load + public async Task OpenUIAsync(Type panelType, string path, AbstractOpenPanelParameter param = null, Action onAsyncGet = null) + { + if (!typeof(BasePanelController).IsAssignableFrom(panelType)) + { + return null; + } + var basePanelController = GetUI(panelType); + + // 如果已有界面(之前缓存过的),则不执行任何操作 + if (basePanelController != null) + { + if (!basePanelController.canvas.enabled) + { + basePanelController.canvas.enabled = true; + } + + onAsyncGet?.Invoke(basePanelController); + + return basePanelController; + } + + ResourceRequest request = Resources.LoadAsync(path); + while (request.isDone == false) + { + await Task.Yield(); + } + + GameObject go = request.asset as GameObject; + var basePanel = Instantiate(go).GetComponent(); + if (basePanel != null) + { + var prefabRectTransform = basePanel.transform as RectTransform; + var anchorMin = prefabRectTransform.anchorMin; + var anchorMax = prefabRectTransform.anchorMax; + var anchoredPosition = prefabRectTransform.anchoredPosition; + var sizeDelta = prefabRectTransform.sizeDelta; + + InternalOnPanelLoaded(panelType, basePanel, param); + if (IsNeedCorrectRectTransform(basePanel)) + { + var panelRectTransform = basePanel.transform as RectTransform; + panelRectTransform.anchorMin = anchorMin; + panelRectTransform.anchorMax = anchorMax; + panelRectTransform.anchoredPosition = anchoredPosition; + panelRectTransform.sizeDelta = sizeDelta; + } + onAsyncGet?.Invoke(basePanel); + + EnsureSpecialPanel(); + return basePanel; + } + else + { + return null; + } + } + + /// + /// Get Or Create UI + /// + /// the prefab path + /// opening param + /// + /// get panel instance if sync mode load + public TPanelController OpenUI(string path, AbstractOpenPanelParameter param = null) where TPanelController : BasePanelController + { + TPanelController basePanelController = GetUI(); + + // 如果已有界面(之前缓存过的),则不执行任何操作 + if (basePanelController != null) + { + if (!basePanelController.canvas.enabled) + { + basePanelController.canvas.enabled = true; + } + + return basePanelController; + } + + var go = Resources.Load(path) as GameObject; + if (go != null) + { + var prefabRectTransform = go.transform as RectTransform; + var anchorMin = prefabRectTransform.anchorMin; + var anchorMax = prefabRectTransform.anchorMax; + var anchoredPosition = prefabRectTransform.anchoredPosition; + var sizeDelta = prefabRectTransform.sizeDelta; + + GameObject panelGo = GameObject.Instantiate(go); + + var basePanel = panelGo.GetComponent(); + if (basePanel == null) { + basePanel = panelGo.AddComponent(); + } + _registerPanels.Add(typeof(TPanelController), basePanel); + + basePanel.transform.SetAsLastSibling(); + if (IsNeedCorrectRectTransform(basePanel)) + { + var panelRectTransform = basePanel.transform as RectTransform; + panelRectTransform.anchorMin = anchorMin; + panelRectTransform.anchorMax = anchorMax; + panelRectTransform.anchoredPosition = anchoredPosition; + panelRectTransform.sizeDelta = sizeDelta; + } + + basePanel.OnLoaded(param); + + EnsureSpecialPanel(); + return basePanel; + } + return null; + } + + /// + /// Get Or Create UI + /// + /// panel type MUST based on BasePanelController + /// the prefab path + /// opening param + /// get panel instance if sync mode load + public BasePanelController OpenUI(Type panelType, string path, AbstractOpenPanelParameter param = null) + { + if (!typeof(BasePanelController).IsAssignableFrom(panelType)) + { + return null; + } + var basePanelController = GetUI(panelType); + + // 如果已有界面(之前缓存过的),则不执行任何操作 + if (basePanelController != null) + { + if (basePanelController != null && !basePanelController.canvas.enabled) + { + basePanelController.canvas.enabled = true; + } + + return basePanelController; + } + + var panelGo = Resources.Load(path) as GameObject; + if (panelGo != null) + { + var prefabRectTransform = panelGo.transform as RectTransform; + var anchorMin = prefabRectTransform.anchorMin; + var anchorMax = prefabRectTransform.anchorMax; + var anchoredPosition = prefabRectTransform.anchoredPosition; + var sizeDelta = prefabRectTransform.sizeDelta; + + var basePanel = GameObject.Instantiate(panelGo).GetComponent(); + + _registerPanels.Add(panelType, basePanel); + + // basePanel.SetOpenOrder(uiOpenOrder); + basePanel.transform.SetAsLastSibling(); + + if (IsNeedCorrectRectTransform(basePanel)) + { + var panelRectTransform = basePanel.transform as RectTransform; + panelRectTransform.anchorMin = anchorMin; + panelRectTransform.anchorMax = anchorMax; + panelRectTransform.anchoredPosition = anchoredPosition; + panelRectTransform.sizeDelta = sizeDelta; + } + basePanel.OnLoaded(param); + + EnsureSpecialPanel(); + return basePanel; + } + return null; + } + + public BasePanelController GetUI(Type panelType) + { + if (!typeof(BasePanelController).IsAssignableFrom(panelType)) + { + return null; + } + + if (_registerPanels.TryGetValue(panelType, out BasePanelController basePanelController)) + { + return basePanelController; + } + + return null; + } + + public TPanelController GetUI() where TPanelController : BasePanelController + { + Type panelType = typeof(TPanelController); + + if (_registerPanels.TryGetValue(panelType, out BasePanelController panel)) + { + return (TPanelController)panel; + } + + return null; + } + + public bool CloseUI(Type panelType) + { + if (!typeof(BasePanelController).IsAssignableFrom(panelType)) + { + return false; + } + BasePanelController baseController = GetUI(panelType); + if (baseController != null) + { + if (panelType == typeof(BasePanelController)) // 标尺界面是测试界面 不用关闭 + return false; + else + baseController.Close(); + return true; + } + return false; + } + + public bool CloseUI() where TPanelController : BasePanelController + { + TPanelController panel = GetUI(); + if (panel != null) + { + panel.Close(); + return true; + } + return false; + } + + /// + /// add ui would invoked after create ui automatically, don't need mannually call it in most case + /// + public void AddUI(BasePanelController panel) + { + if (panel == null) + { + return; + } + var rt = panel.transform as RectTransform; + Vector2 cacheAP = Vector2.zero; + Vector2 cacheOffsetMax = Vector2.zero; + Vector2 cacheOffsetMin = Vector2.zero; + Vector2 cacheSizeDelta = Vector2.zero; + if (rt != null) + { + cacheAP = rt.anchoredPosition; + cacheOffsetMax = rt.offsetMax; + cacheOffsetMin = rt.offsetMin; + cacheSizeDelta = rt.sizeDelta; + } + panel.transform.SetParent(panel.AttachedParent); + if (rt != null) + { + rt.anchoredPosition = cacheAP; + rt.offsetMax = cacheOffsetMax; + rt.offsetMin = cacheOffsetMin; + rt.sizeDelta = cacheSizeDelta; + } + panel.transform.localScale = Vector3.one; + panel.transform.localRotation = Quaternion.identity; + panel.gameObject.name = panel.gameObject.name.Replace("(Clone)", ""); + } + + /// + /// remove ui would invoked automatically, don't need mannually call it in most case + /// + public void RemoveUI(BasePanelController panel) + { + if (panel == null) + { + return; + } + Type panelType = panel.GetType(); + if (_registerPanels.ContainsKey(panelType)) + { + _registerPanels.Remove(panelType); + } + + if (panelType == typeof(ToastBlackPanelController) || panelType == typeof(ToastWhitePanelController)) { + CheckToastCache(); + } + + panel.Dispose(); + } + + private void CheckToastCache() { + if (_toastCacheData.Count > 0) { + var config = _toastCacheData[0]; + InternalOpenToast(config.white, config.text, config.popupTime, config.icon); + _toastCacheData.RemoveAt(0); + } + } + + /// + /// take some ui to the most top layer + /// + /// + public void ToppedUI(Type panelType) + { + if (!typeof(BasePanelController).IsAssignableFrom(panelType)) + { + return; + } + ToppedUI(GetUI(panelType)); + } + + /// + /// take some ui to the most top layer + /// + /// + /// 特殊 UI(Loading,Toast之类) 是否需要重新制定 + public void ToppedUI(BasePanelController panel, bool toppedSepcialUI = true) + { + if (panel == null) + { + return; + } + + // panel.SetOpenOrder(uiOpenOrder); + panel.transform.SetAsLastSibling(); + if (toppedSepcialUI) EnsureSpecialPanel(); + } + + /// + /// open toast panel for tip info + /// + public void OpenToast(bool white, string text, float popupTime = 3.0f, Texture icon = null) { + // print this function parameters + if (!string.IsNullOrEmpty(text) && popupTime > 0) { + bool isShowing = GetUI() != null || GetUI() != null; + if (ToastTryMerge(white, text, popupTime, icon)) { + return; + } + if (isShowing) { + var config = new ToastConfig() { + white = white, + text = text, + popupTime = popupTime, + icon = icon, + }; + _toastCacheData.Add(config); + } + else { + InternalOpenToast(white, text, popupTime, icon); + } + } + } + + private bool ToastTryMerge(bool white, string text, float popupTime, Texture icon) { + var currentToastPanel = GetUI(); + if (currentToastPanel?.toastParam != null) { + var param = currentToastPanel?.toastParam; + // 目前只有白色 Toast 进行 Merge + bool sameToast = param.icon == icon && param.text == text && white; + if (sameToast) { + currentToastPanel.Refresh(popupTime); + return true; + } + } + + return false; + } + + /// + /// 打开通用 Toast(白色底) + /// + /// + /// 设置 Toast 提示级别,会展示提示图片,如果不想要提示图片,请使用 GeneralToastLevel.None + /// + public void OpenToast(string text, GeneralToastLevel toastLevel, float popupTime = 3.0f) { + OpenToast(true, text, popupTime, GetToastIcon(toastLevel)); + } + + private Texture GetToastIcon(GeneralToastLevel toastLevel) { + if (toastLevel == GeneralToastLevel.None) + return null; + switch (toastLevel) { + case GeneralToastLevel.Info: + return UIManager.WhiteToastInfoIcon; + case GeneralToastLevel.Warning: + return UIManager.WhiteToastWarningIcon; + case GeneralToastLevel.Error: + return UIManager.WhiteToastErrorIcon; + case GeneralToastLevel.Success: + return UIManager.WhiteToastSuccessIcon; + } + + return null; + } + + private void InternalOpenToast(bool white, string text, float popupTime, Texture icon) { + if (string.IsNullOrEmpty(text) || popupTime <= 0) return; + if (white) { + var toast = OpenUI("TapCommonToastWhite", new ToastPanelOpenParam(text, popupTime, icon)); + if (toast != null) { + toast.transform.SetAsLastSibling(); + } + } + else { + var toast = OpenUI("TapCommonToastBlack", new ToastPanelOpenParam(text, popupTime, icon)); + if (toast != null) { + toast.transform.SetAsLastSibling(); + } + } + } + + /// + /// open toast panel for tip info + /// + public void OpenTip(string text, Color textColor, TextAnchor textAnchor = TextAnchor.MiddleCenter) + { + if (!string.IsNullOrEmpty(text)) + { + Debug.LogFormat($"[UIManager] Call OpenTip text:{text} "); + var tip = OpenUI("TapCommonTip", new TipPanelOpenParam(text, textColor, textAnchor)); + if (tip != null) + { + tip.transform.SetAsLastSibling(); + } + } + } + + /// + /// open loading panel that would at the toppest layer and block interaction + /// + public void OpenLoading() + { + var loadingPanel = OpenUI("Loading"); + if (loadingPanel != null) + { + // https://www.reddit.com/r/Unity3D/comments/2b1g1l/order_in_layer_maximum_value/ + // loadingPanel.SetOpenOrder(LOADING_PANEL_SORTING_ORDER); + loadingPanel.transform.SetAsLastSibling(); + } + } + + /// + /// open loading panel that would at the toppest layer and block interaction + /// + public async Task OpenLoadingAsync() + { + var loadingPanel = await OpenUIAsync("Loading"); + if (loadingPanel != null) + { + // https://www.reddit.com/r/Unity3D/comments/2b1g1l/order_in_layer_maximum_value/ + // loadingPanel.SetOpenOrder(LOADING_PANEL_SORTING_ORDER); + loadingPanel.transform.SetAsLastSibling(); + } + } + + public void CloseLoading() + { + var loadingPanel = GetUI(); + if (loadingPanel != null) + { + loadingPanel.Close(); + } + } + + public void CloseTip() + { + var panel = GetUI(); + if (panel != null) + { + panel.Close(); + } + } + + private bool IsNeedCorrectRectTransform(BasePanelController controller) + { + if (controller == null) return false; + return (controller.panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.None; + } + + private void InternalOnPanelLoaded(Type tPanelController, BasePanelController basePanel, AbstractOpenPanelParameter param = null) + { + _registerPanels.Add(tPanelController, basePanel); + + basePanel.OnLoaded(param); + + // basePanel.SetOpenOrder(uiOpenOrder); + + basePanel.transform.SetAsLastSibling(); + } + + private void EnsureSpecialPanel() + { + var temp = _registerPanels.Where(panel => + panel.Value != null && panel.Value.gameObject.activeInHierarchy && panel.Value.toppedOrder != 0); + var toppedPanels = new List>(temp); + if (toppedPanels.Count == 0) return; + toppedPanels.Sort((x,y)=>x.Value.toppedOrder.CompareTo(y.Value.toppedOrder)); + foreach (var toppedPanel in toppedPanels) + { + toppedPanel.Value.transform.SetAsLastSibling(); + } + } + + public Transform GetUIRootTransform(BasePanelController panel) + { + if (panel?.openParam != null && panel.openParam.NeedConstantResolution) + return _uiConstantRoot.transform; + else + return _uiRoot.transform; + } + + + #region external api + public void SetSortOrder(int order) + { + _uiRootCanvas.sortingOrder = order; + } + + public Camera GetUICamera() + { + return _uiCamera; + } + #endregion + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Base/UIManager.cs.meta b/Runtime/Internal/UI/Base/UIManager.cs.meta new file mode 100644 index 0000000..1d2fe92 --- /dev/null +++ b/Runtime/Internal/UI/Base/UIManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 753f42bc9ae8a4035ae94f4733957527 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/BasePanel.meta b/Runtime/Internal/UI/BasePanel.meta new file mode 100644 index 0000000..22362e9 --- /dev/null +++ b/Runtime/Internal/UI/BasePanel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 930467091d41f45c09b2d67e0a3ced30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/BasePanel/BasePanelController.cs b/Runtime/Internal/UI/BasePanel/BasePanelController.cs new file mode 100644 index 0000000..891ce55 --- /dev/null +++ b/Runtime/Internal/UI/BasePanel/BasePanelController.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.UI; + +namespace TapSDK.UI +{ + /// + /// base panel of TapSDK UI module + /// + [RequireComponent(typeof(CanvasGroup))] + [RequireComponent(typeof(GraphicRaycaster))] + public abstract class BasePanelController : MonoBehaviour + { + protected virtual float GetToastSlideInOffset() => 300; + /// + /// the canvas related to this panel + /// + [HideInInspector] + public Canvas canvas; + + /// + /// the canvas group related to this panel + /// + [HideInInspector] + public CanvasGroup canvasGroup; + + /// + /// fade in/out time + /// + protected float fadeAnimationTime = 0.15f; + + /// + /// animation elapse time + /// + private float _animationElapse; + + private Vector2 _screenSize; + private Vector2 _cachedAnchorPos; + + private RectTransform _rectTransform; + + private Coroutine _animationCoroutine; + + /// + /// open parameter + /// + protected internal AbstractOpenPanelParameter openParam; + + /// + /// settings about this panel + /// + public BasePanelConfig panelConfig; + + /// + /// 特殊面板需要一直保持置顶的,需要填写 toppedOrder, toppedOrder 越大越置顶 + /// + public int toppedOrder; + + /// + /// the transform parent when created it would be attached to + /// + /// + public virtual Transform AttachedParent => UIManager.Instance.GetUIRootTransform(this); + + #region Load + protected virtual void Awake() + { + canvas = GetComponent(); + canvasGroup = GetComponent(); + _rectTransform = transform as RectTransform; + + _screenSize = new Vector2(Screen.width, Screen.height); + + #if UNITY_EDITOR + if (canvas == null) + { + Debug.LogErrorFormat("[TapSDK UI] BasePanel Must Be Related To Canvas Component!"); + } + #endif + } + + /// + /// bind ugui components for every panel + /// + protected virtual void BindComponents() {} + + /// + /// create the prefab instance + /// + /// + public void OnLoaded(AbstractOpenPanelParameter param = null) + { + openParam = param; + // 寻找组件 + BindComponents(); + + // 添加到控制层 + UIManager.Instance.AddUI(this); + + // 更新层级信息 + InitCanvasSetting(); + // 开始动画效果 + OnShowEffectStart(); + + // 调用加载成功方法 + OnLoadSuccess(); + } + + private void InitCanvasSetting() + { + if (canvas.renderMode != RenderMode.ScreenSpaceOverlay) + { + var camera = UIManager.Instance.GetUICamera(); + if (camera != null) + { + canvas.worldCamera = camera; + } + } + + canvas.pixelPerfect = true; + // canvas.overrideSorting = true; + } + + /// + /// init panel logic here + /// + protected virtual void OnLoadSuccess() + { + + } + + #endregion + + #region Animation + + protected virtual void OnShowEffectStart() + { + if (panelConfig.animationType == EAnimationMode.None) + { + return; + } + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = 0; + } + + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = Vector3.zero; + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + _cachedAnchorPos = _rectTransform.anchoredPosition; + _rectTransform.anchoredPosition += new Vector2(_screenSize.x, 0); + } + + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) { + _cachedAnchorPos = _rectTransform.anchoredPosition; + _rectTransform.anchoredPosition += new Vector2(0, _screenSize.y); + } + + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) { + _cachedAnchorPos = _rectTransform.anchoredPosition; + _rectTransform.anchoredPosition += new Vector2(0, GetToastSlideInOffset()); + } + OnEffectStart(); + _animationCoroutine = StartCoroutine(FadeInCoroutine(fadeAnimationTime)); + } + + protected virtual void OnShowEffectEnd() + { + OnEffectEnd(); + } + + protected virtual void OnCloseEffectStart() + { + OnEffectStart(); + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = 1; + } + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = Vector3.one; + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + _animationCoroutine = StartCoroutine(FadeOutCoroutine(fadeAnimationTime)); + } + + protected virtual void OnCloseEffectEnd() + { + OnEffectEnd(); + GameObject.Destroy(gameObject); + } + + private void OnEffectStart() + { + _animationElapse = 0; + if (_animationCoroutine != null) + { + StopCoroutine(_animationCoroutine); + _animationCoroutine = null; + } + canvasGroup.interactable = false; + } + + private void OnEffectEnd() + { + canvasGroup.interactable = true; + _animationElapse = 0; + _animationCoroutine = null; + } + + private IEnumerator FadeInCoroutine(float time) + { + while (_animationElapse < time) + { + yield return null; + _animationElapse += Time.deltaTime; + float value = Mathf.Clamp01(_animationElapse / time); + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = value; + } + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = new Vector3(value, value, value); + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + var temp = (1 - value) * _screenSize.x; + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + temp, _cachedAnchorPos.y); + } + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) + { + var temp = (1 - value) * _screenSize.y; + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + + temp); + } + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) + { + var temp = (1 - value) * GetToastSlideInOffset(); + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + + temp); + } + } + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = 1; + } + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = Vector3.one; + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) + { + _rectTransform.anchoredPosition = _cachedAnchorPos; + } + + OnShowEffectEnd(); + } + + private IEnumerator FadeOutCoroutine(float time) + { + while (_animationElapse < time) + { + yield return null; + _animationElapse += Time.deltaTime; + float value = 1 - Mathf.Clamp01(_animationElapse / time); + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = value; + } + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = new Vector3(value, value, value); + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + var temp = (1 - value) * _screenSize.x; + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + temp, _cachedAnchorPos.y); + } + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) + { + var temp = (1 - value) * _screenSize.y; + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + temp); + } + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) + { + var temp = (1 - value) * GetToastSlideInOffset(); + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + temp); + } + } + + if ((panelConfig.animationType & EAnimationMode.Alpha) == EAnimationMode.Alpha) + { + canvasGroup.alpha = 0; + } + if ((panelConfig.animationType & EAnimationMode.Scale) == EAnimationMode.Scale) + { + transform.localScale = Vector3.zero; + } + if ((panelConfig.animationType & EAnimationMode.RightSlideIn) == EAnimationMode.RightSlideIn) + { + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x + _screenSize.x, _cachedAnchorPos.y); + } + if ((panelConfig.animationType & EAnimationMode.UpSlideIn) == EAnimationMode.UpSlideIn) + { + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + _screenSize.y); + } + if ((panelConfig.animationType & EAnimationMode.ToastSlideIn) == EAnimationMode.ToastSlideIn) + { + _rectTransform.anchoredPosition = new Vector2(_cachedAnchorPos.x, _cachedAnchorPos.y + GetToastSlideInOffset()); + } + + OnCloseEffectEnd(); + } + + #endregion + + + /// + /// on receive resolution change event + /// + /// + protected virtual void UIAdapt(Vector2Int res) {} + + /// + /// common close api + /// + public virtual void Close() + { + UIManager.Instance.RemoveUI(this); + } + + /// + /// set canvas sorting order + /// + /// + public void SetOpenOrder(int openOrder) + { + if (canvas != null) + { + canvas.sortingOrder = openOrder; + } + } + + /// + /// also would destroy panel gameObject + /// + public virtual void Dispose() + { + if (panelConfig.animationType == EAnimationMode.None) + { + GameObject.Destroy(gameObject); + return; + } + + OnCloseEffectStart(); + } + } +} diff --git a/Runtime/Internal/UI/BasePanel/BasePanelController.cs.meta b/Runtime/Internal/UI/BasePanel/BasePanelController.cs.meta new file mode 100644 index 0000000..9bedeae --- /dev/null +++ b/Runtime/Internal/UI/BasePanel/BasePanelController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 607d7f7e1e2a44319bfc7d85788fd805 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Params.meta b/Runtime/Internal/UI/Params.meta new file mode 100644 index 0000000..2d0df8f --- /dev/null +++ b/Runtime/Internal/UI/Params.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 00f81d135c9e44ec4b4a064781b1bcd0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Params/BasePanelConfig.cs b/Runtime/Internal/UI/Params/BasePanelConfig.cs new file mode 100644 index 0000000..4a853b0 --- /dev/null +++ b/Runtime/Internal/UI/Params/BasePanelConfig.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +namespace TapSDK.UI +{ + [System.Serializable] + public struct BasePanelConfig + { + /// + /// animation effect related to opening and closing + /// + public EAnimationMode animationType; + + public BasePanelConfig(EAnimationMode animationMode = EAnimationMode.None) + { + animationType = animationMode; + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Params/BasePanelConfig.cs.meta b/Runtime/Internal/UI/Params/BasePanelConfig.cs.meta new file mode 100644 index 0000000..380c30f --- /dev/null +++ b/Runtime/Internal/UI/Params/BasePanelConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35a0b66e8719e4104bf30ae06108d41a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/Params/IOpenPanelParameter.cs b/Runtime/Internal/UI/Params/IOpenPanelParameter.cs new file mode 100644 index 0000000..92c5ea8 --- /dev/null +++ b/Runtime/Internal/UI/Params/IOpenPanelParameter.cs @@ -0,0 +1,7 @@ +namespace TapSDK.UI +{ + public abstract class AbstractOpenPanelParameter + { + public virtual bool NeedConstantResolution => false; + } +} \ No newline at end of file diff --git a/Runtime/Internal/UI/Params/IOpenPanelParameter.cs.meta b/Runtime/Internal/UI/Params/IOpenPanelParameter.cs.meta new file mode 100644 index 0000000..17569a8 --- /dev/null +++ b/Runtime/Internal/UI/Params/IOpenPanelParameter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5b19f6b895fa4059a0e3b7aaf9581c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx.meta b/Runtime/Internal/UI/ScrollViewEx.meta new file mode 100644 index 0000000..8532160 --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5124d8bb5646548db8970683b0a33abd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx/ObjPool.meta b/Runtime/Internal/UI/ScrollViewEx/ObjPool.meta new file mode 100644 index 0000000..971bc53 --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ObjPool.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 765cb640e73334f7a9ccc676064c70be +folderAsset: yes +timeCreated: 1533284978 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs b/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs new file mode 100644 index 0000000..d2a80e6 --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs @@ -0,0 +1,76 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) AillieoTech. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace TapSDK.UI.AillieoTech +{ + using System; + using System.Collections.Generic; + + public class SimpleObjPool + { + private readonly Stack stack; + private readonly Func ctor; + private readonly Action onRecycle; + private int size; + private int usedCount; + + public SimpleObjPool(int max = 7, Action onRecycle = null, Func ctor = null) + { + this.stack = new Stack(max); + this.size = max; + this.onRecycle = onRecycle; + this.ctor = ctor; + } + + public T Get() + { + T item; + if (this.stack.Count == 0) + { + if (this.ctor != null) + { + item = this.ctor(); + } + else + { + item = Activator.CreateInstance(); + } + } + else + { + item = this.stack.Pop(); + } + + this.usedCount++; + return item; + } + + public void Recycle(T item) + { + if (this.onRecycle != null) + { + this.onRecycle.Invoke(item); + } + + if (this.stack.Count < this.size) + { + this.stack.Push(item); + } + + this.usedCount--; + } + + public void Purge() + { + // TODO + } + + public override string ToString() + { + return $"SimpleObjPool: item=[{typeof(T)}], inUse=[{this.usedCount}], restInPool=[{this.stack.Count}/{this.size}] "; + } + } +} diff --git a/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs.meta b/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs.meta new file mode 100644 index 0000000..159383b --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ObjPool/SimpleObjPool.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 88625144948bd48d296916f8b0e9b41a +timeCreated: 1533284979 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx/ScrollView.meta b/Runtime/Internal/UI/ScrollViewEx/ScrollView.meta new file mode 100644 index 0000000..732375b --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ScrollView.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ab23454192f22412c8ae6f7699645396 +folderAsset: yes +timeCreated: 1533284978 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs new file mode 100644 index 0000000..7a1fe91 --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs @@ -0,0 +1,828 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) AillieoTech. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace TapSDK.UI.AillieoTech +{ + using System; + using System.Collections; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.Serialization; + using UnityEngine.UI; + + [RequireComponent(typeof(RectTransform))] + [DisallowMultipleComponent] + public class ScrollView : ScrollRect + { + [Tooltip("默认item尺寸")] + public Vector2 defaultItemSize; + + [Tooltip("item的模板")] + public RectTransform itemTemplate; + + // 0001 + protected const int flagScrollDirection = 1; + + [SerializeField] + [FormerlySerializedAs("m_layoutType")] + protected ItemLayoutType layoutType = ItemLayoutType.Vertical; + + // 只保存4个临界index + protected int[] criticalItemIndex = new int[4]; + + // callbacks for items + protected Action updateFunc; + protected Func itemSizeFunc; + protected Func itemCountFunc; + protected Func itemGetFunc; + protected Action itemRecycleFunc; + + private readonly List managedItems = new List(); + + private Rect refRect; + + // resource management + private SimpleObjPool itemPool = null; + + private int dataCount = 0; + + [Tooltip("初始化时池内item数量")] + [SerializeField] + private int poolSize; + + // status + private bool initialized = false; + private int willUpdateData = 0; + + private Vector3[] viewWorldConers = new Vector3[4]; + private Vector3[] rectCorners = new Vector3[2]; + + // for hide and show + public enum ItemLayoutType + { + // 最后一位表示滚动方向 + Vertical = 0b0001, // 0001 + Horizontal = 0b0010, // 0010 + VerticalThenHorizontal = 0b0100, // 0100 + HorizontalThenVertical = 0b0101, // 0101 + } + + public virtual void SetUpdateFunc(Action func) + { + this.updateFunc = func; + } + + public virtual void SetItemSizeFunc(Func func) + { + this.itemSizeFunc = func; + } + + public virtual void SetItemCountFunc(Func func) + { + this.itemCountFunc = func; + } + + public void SetItemGetAndRecycleFunc(Func getFunc, Action recycleFunc) + { + if (getFunc != null && recycleFunc != null) + { + this.itemGetFunc = getFunc; + this.itemRecycleFunc = recycleFunc; + } + else + { + this.itemGetFunc = null; + this.itemRecycleFunc = null; + } + } + + public void ResetAllDelegates() + { + this.SetUpdateFunc(null); + this.SetItemSizeFunc(null); + this.SetItemCountFunc(null); + this.SetItemGetAndRecycleFunc(null, null); + } + + public void UpdateData(bool immediately = true) + { + if (immediately) + { + this.willUpdateData |= 3; // 0011 + this.InternalUpdateData(); + } + else + { + if (this.willUpdateData == 0 && this.IsActive()) + { + this.StartCoroutine(this.DelayUpdateData()); + } + + this.willUpdateData |= 3; + } + } + + public void UpdateDataIncrementally(bool immediately = true) + { + if (immediately) + { + this.willUpdateData |= 1; // 0001 + this.InternalUpdateData(); + } + else + { + if (this.willUpdateData == 0) + { + this.StartCoroutine(this.DelayUpdateData()); + } + + this.willUpdateData |= 1; + } + } + + public void ScrollTo(int index) + { + this.InternalScrollTo(index); + } + + protected override void OnEnable() + { + base.OnEnable(); + if (this.willUpdateData != 0) + { + this.StartCoroutine(this.DelayUpdateData()); + } + } + + protected override void OnDisable() + { + this.initialized = false; + base.OnDisable(); + } + + protected virtual void InternalScrollTo(int index) + { + index = Mathf.Clamp(index, 0, this.dataCount - 1); + this.EnsureItemRect(index); + Rect r = this.managedItems[index].rect; + + var dir = (int)this.layoutType & flagScrollDirection; + if (dir == 1) + { + // vertical + var value = 1 - (-r.yMax / (this.content.sizeDelta.y - this.refRect.height)); + this.SetNormalizedPosition(value, 1); + } + else + { + // horizontal + var value = r.xMin / (this.content.sizeDelta.x - this.refRect.width); + this.SetNormalizedPosition(value, 0); + } + } + + protected override void SetContentAnchoredPosition(Vector2 position) + { + base.SetContentAnchoredPosition(position); + this.UpdateCriticalItems(); + } + + protected override void SetNormalizedPosition(float value, int axis) + { + base.SetNormalizedPosition(value, axis); + this.ResetCriticalItems(); + } + + protected void EnsureItemRect(int index) + { + if (!this.managedItems[index].rectDirty) + { + // 已经是干净的了 + return; + } + + ScrollItemWithRect firstItem = this.managedItems[0]; + if (firstItem.rectDirty) + { + Vector2 firstSize = this.GetItemSize(0); + firstItem.rect = CreateWithLeftTopAndSize(Vector2.zero, firstSize); + firstItem.rectDirty = false; + } + + // 当前item之前的最近的已更新的rect + var nearestClean = 0; + for (var i = index; i >= 0; --i) + { + if (!this.managedItems[i].rectDirty) + { + nearestClean = i; + break; + } + } + + // 需要更新 从 nearestClean 到 index 的尺寸 + Rect nearestCleanRect = this.managedItems[nearestClean].rect; + Vector2 curPos = GetLeftTop(nearestCleanRect); + Vector2 size = nearestCleanRect.size; + this.MovePos(ref curPos, size); + + for (var i = nearestClean + 1; i <= index; i++) + { + size = this.GetItemSize(i); + this.managedItems[i].rect = CreateWithLeftTopAndSize(curPos, size); + this.managedItems[i].rectDirty = false; + this.MovePos(ref curPos, size); + } + + var range = new Vector2(Mathf.Abs(curPos.x), Mathf.Abs(curPos.y)); + switch (this.layoutType) + { + case ItemLayoutType.VerticalThenHorizontal: + range.x += size.x; + range.y = this.refRect.height; + break; + case ItemLayoutType.HorizontalThenVertical: + range.x = this.refRect.width; + if (curPos.x != 0) + { + range.y += size.y; + } + + break; + default: + break; + } + + this.content.sizeDelta = range; + } + + protected override void OnDestroy() + { + if (this.itemPool != null) + { + this.itemPool.Purge(); + } + } + + protected Rect GetItemLocalRect(int index) + { + if (index >= 0 && index < this.dataCount) + { + this.EnsureItemRect(index); + return this.managedItems[index].rect; + } + + return (Rect)default; + } + +#if UNITY_EDITOR + protected override void OnValidate() + { + var dir = (int)this.layoutType & flagScrollDirection; + if (dir == 1) + { + // vertical + if (this.horizontalScrollbar != null) + { + this.horizontalScrollbar.gameObject.SetActive(false); + this.horizontalScrollbar = null; + } + } + else + { + // horizontal + if (this.verticalScrollbar != null) + { + this.verticalScrollbar.gameObject.SetActive(false); + this.verticalScrollbar = null; + } + } + + base.OnValidate(); + } +#endif + + private static Vector2 GetLeftTop(Rect rect) + { + Vector2 ret = rect.position; + ret.y += rect.size.y; + return ret; + } + + private static Rect CreateWithLeftTopAndSize(Vector2 leftTop, Vector2 size) + { + Vector2 leftBottom = leftTop - new Vector2(0, size.y); + return new Rect(leftBottom, size); + } + + private IEnumerator DelayUpdateData() + { + yield return new WaitForEndOfFrame(); + this.InternalUpdateData(); + } + + private void InternalUpdateData() + { + if (!this.IsActive()) + { + this.willUpdateData |= 3; + return; + } + + if (!this.initialized) + { + this.InitScrollView(); + } + + var newDataCount = 0; + var keepOldItems = (this.willUpdateData & 2) == 0; + + if (this.itemCountFunc != null) + { + newDataCount = this.itemCountFunc(); + } + + if (newDataCount != this.managedItems.Count) + { + if (this.managedItems.Count < newDataCount) + { + // 增加 + if (!keepOldItems) + { + foreach (var itemWithRect in this.managedItems) + { + // 重置所有rect + itemWithRect.rectDirty = true; + } + } + + while (this.managedItems.Count < newDataCount) + { + this.managedItems.Add(new ScrollItemWithRect()); + } + } + else + { + // 减少 保留空位 避免GC + for (int i = 0, count = this.managedItems.Count; i < count; ++i) + { + if (i < newDataCount) + { + // 重置所有rect + if (!keepOldItems) + { + this.managedItems[i].rectDirty = true; + } + + if (i == newDataCount - 1) + { + this.managedItems[i].rectDirty = true; + } + } + + // 超出部分 清理回收item + if (i >= newDataCount) + { + this.managedItems[i].rectDirty = true; + if (this.managedItems[i].item != null) + { + this.RecycleOldItem(this.managedItems[i].item); + this.managedItems[i].item = null; + } + } + } + } + } + else + { + if (!keepOldItems) + { + for (int i = 0, count = this.managedItems.Count; i < count; ++i) + { + // 重置所有rect + this.managedItems[i].rectDirty = true; + } + } + } + + this.dataCount = newDataCount; + + this.ResetCriticalItems(); + + this.willUpdateData = 0; + } + + private void ResetCriticalItems() + { + bool hasItem, shouldShow; + int firstIndex = -1, lastIndex = -1; + + for (var i = 0; i < this.dataCount; i++) + { + hasItem = this.managedItems[i].item != null; + shouldShow = this.ShouldItemSeenAtIndex(i); + + if (shouldShow) + { + if (firstIndex == -1) + { + firstIndex = i; + } + + lastIndex = i; + } + + if (hasItem && shouldShow) + { + // 应显示且已显示 + this.SetDataForItemAtIndex(this.managedItems[i].item, i); + continue; + } + + if (hasItem == shouldShow) + { + // 不应显示且未显示 + // if (firstIndex != -1) + // { + // // 已经遍历完所有要显示的了 后边的先跳过 + // break; + // } + continue; + } + + if (hasItem && !shouldShow) + { + // 不该显示 但是有 + this.RecycleOldItem(this.managedItems[i].item); + this.managedItems[i].item = null; + continue; + } + + if (shouldShow && !hasItem) + { + // 需要显示 但是没有 + RectTransform item = this.GetNewItem(i); + this.OnGetItemForDataIndex(item, i); + this.managedItems[i].item = item; + continue; + } + } + + // content.localPosition = Vector2.zero; + this.criticalItemIndex[CriticalItemType.UpToHide] = firstIndex; + this.criticalItemIndex[CriticalItemType.DownToHide] = lastIndex; + this.criticalItemIndex[CriticalItemType.UpToShow] = Mathf.Max(firstIndex - 1, 0); + this.criticalItemIndex[CriticalItemType.DownToShow] = Mathf.Min(lastIndex + 1, this.dataCount - 1); + } + + private RectTransform GetCriticalItem(int type) + { + var index = this.criticalItemIndex[type]; + if (index >= 0 && index < this.dataCount) + { + return this.managedItems[index].item; + } + + return null; + } + + private void UpdateCriticalItems() + { + var dirty = true; + + while (dirty) + { + dirty = false; + + for (int i = CriticalItemType.UpToHide; i <= CriticalItemType.DownToShow; i++) + { + if (i <= CriticalItemType.DownToHide) + { + // 隐藏离开可见区域的item + dirty = dirty || this.CheckAndHideItem(i); + } + else + { + // 显示进入可见区域的item + dirty = dirty || this.CheckAndShowItem(i); + } + } + } + } + + private bool CheckAndHideItem(int criticalItemType) + { + RectTransform item = this.GetCriticalItem(criticalItemType); + var criticalIndex = this.criticalItemIndex[criticalItemType]; + if (item != null && !this.ShouldItemSeenAtIndex(criticalIndex)) + { + this.RecycleOldItem(item); + this.managedItems[criticalIndex].item = null; + + if (criticalItemType == CriticalItemType.UpToHide) + { + // 最上隐藏了一个 + this.criticalItemIndex[criticalItemType + 2] = Mathf.Max(criticalIndex, this.criticalItemIndex[criticalItemType + 2]); + this.criticalItemIndex[criticalItemType]++; + } + else + { + // 最下隐藏了一个 + this.criticalItemIndex[criticalItemType + 2] = Mathf.Min(criticalIndex, this.criticalItemIndex[criticalItemType + 2]); + this.criticalItemIndex[criticalItemType]--; + } + + this.criticalItemIndex[criticalItemType] = Mathf.Clamp(this.criticalItemIndex[criticalItemType], 0, this.dataCount - 1); + + if (this.criticalItemIndex[CriticalItemType.UpToHide] > this.criticalItemIndex[CriticalItemType.DownToHide]) + { + // 偶然的情况 拖拽超出一屏 + this.ResetCriticalItems(); + return false; + } + + return true; + } + + return false; + } + + private bool CheckAndShowItem(int criticalItemType) + { + RectTransform item = this.GetCriticalItem(criticalItemType); + var criticalIndex = this.criticalItemIndex[criticalItemType]; + + if (item == null && this.ShouldItemSeenAtIndex(criticalIndex)) + { + RectTransform newItem = this.GetNewItem(criticalIndex); + this.OnGetItemForDataIndex(newItem, criticalIndex); + this.managedItems[criticalIndex].item = newItem; + + if (criticalItemType == CriticalItemType.UpToShow) + { + // 最上显示了一个 + this.criticalItemIndex[criticalItemType - 2] = Mathf.Min(criticalIndex, this.criticalItemIndex[criticalItemType - 2]); + this.criticalItemIndex[criticalItemType]--; + } + else + { + // 最下显示了一个 + this.criticalItemIndex[criticalItemType - 2] = Mathf.Max(criticalIndex, this.criticalItemIndex[criticalItemType - 2]); + this.criticalItemIndex[criticalItemType]++; + } + + this.criticalItemIndex[criticalItemType] = Mathf.Clamp(this.criticalItemIndex[criticalItemType], 0, this.dataCount - 1); + + if (this.criticalItemIndex[CriticalItemType.UpToShow] >= this.criticalItemIndex[CriticalItemType.DownToShow]) + { + // 偶然的情况 拖拽超出一屏 + this.ResetCriticalItems(); + return false; + } + + return true; + } + + return false; + } + + private bool ShouldItemSeenAtIndex(int index) + { + if (index < 0 || index >= this.dataCount) + { + return false; + } + + this.EnsureItemRect(index); + return new Rect(this.refRect.position - this.content.anchoredPosition, this.refRect.size).Overlaps(this.managedItems[index].rect); + } + + private bool ShouldItemFullySeenAtIndex(int index) + { + if (index < 0 || index >= this.dataCount) + { + return false; + } + + this.EnsureItemRect(index); + return this.IsRectContains(new Rect(this.refRect.position - this.content.anchoredPosition, this.refRect.size), this.managedItems[index].rect); + } + + private bool IsRectContains(Rect outRect, Rect inRect, bool bothDimensions = false) + { + if (bothDimensions) + { + var xContains = (outRect.xMax >= inRect.xMax) && (outRect.xMin <= inRect.xMin); + var yContains = (outRect.yMax >= inRect.yMax) && (outRect.yMin <= inRect.yMin); + return xContains && yContains; + } + else + { + var dir = (int)this.layoutType & flagScrollDirection; + if (dir == 1) + { + // 垂直滚动 只计算y向 + return (outRect.yMax >= inRect.yMax) && (outRect.yMin <= inRect.yMin); + } + else + { + // = 0 + // 水平滚动 只计算x向 + return (outRect.xMax >= inRect.xMax) && (outRect.xMin <= inRect.xMin); + } + } + } + + private void InitPool() + { + var poolNode = new GameObject("POOL"); + poolNode.SetActive(false); + poolNode.transform.SetParent(this.transform, false); + this.itemPool = new SimpleObjPool( + this.poolSize, + (RectTransform item) => + { + item.transform.SetParent(poolNode.transform, false); + }, + () => + { + GameObject itemObj = Instantiate(this.itemTemplate.gameObject); + RectTransform item = itemObj.GetComponent(); + itemObj.transform.SetParent(poolNode.transform, false); + + item.anchorMin = Vector2.up; + item.anchorMax = Vector2.up; + item.pivot = Vector2.zero; + + itemObj.SetActive(true); + return item; + }); + } + + private void OnGetItemForDataIndex(RectTransform item, int index) + { + this.SetDataForItemAtIndex(item, index); + item.transform.SetParent(this.content, false); + } + + private void SetDataForItemAtIndex(RectTransform item, int index) + { + if (this.updateFunc != null) + { + this.updateFunc(index, item); + } + + this.SetPosForItemAtIndex(item, index); + } + + private void SetPosForItemAtIndex(RectTransform item, int index) + { + this.EnsureItemRect(index); + Rect r = this.managedItems[index].rect; + item.localPosition = r.position; + item.sizeDelta = r.size; + } + + private Vector2 GetItemSize(int index) + { + if (index >= 0 && index <= this.dataCount) + { + if (this.itemSizeFunc != null) + { + return this.itemSizeFunc(index); + } + } + + return this.defaultItemSize; + } + + private RectTransform GetNewItem(int index) + { + RectTransform item; + if (this.itemGetFunc != null) + { + item = this.itemGetFunc(index); + } + else + { + item = this.itemPool.Get(); + } + + return item; + } + + private void RecycleOldItem(RectTransform item) + { + if (this.itemRecycleFunc != null) + { + this.itemRecycleFunc(item); + } + else + { + this.itemPool.Recycle(item); + } + } + + private void InitScrollView() + { + this.initialized = true; + + // 根据设置来控制原ScrollRect的滚动方向 + var dir = (int)this.layoutType & flagScrollDirection; + this.vertical = dir == 1; + this.horizontal = dir == 0; + + this.content.pivot = Vector2.up; + this.content.anchorMin = Vector2.up; + this.content.anchorMax = Vector2.up; + this.content.anchoredPosition = Vector2.zero; + + this.InitPool(); + this.UpdateRefRect(); + } + + // refRect是在Content节点下的 viewport的 rect + private void UpdateRefRect() + { + /* + * WorldCorners + * + * 1 ------- 2 + * | | + * | | + * 0 ------- 3 + * + */ + + if (!CanvasUpdateRegistry.IsRebuildingLayout()) + { + Canvas.ForceUpdateCanvases(); + } + + this.viewRect.GetWorldCorners(this.viewWorldConers); + this.rectCorners[0] = this.content.transform.InverseTransformPoint(this.viewWorldConers[0]); + this.rectCorners[1] = this.content.transform.InverseTransformPoint(this.viewWorldConers[2]); + this.refRect = new Rect((Vector2)this.rectCorners[0] - this.content.anchoredPosition, this.rectCorners[1] - this.rectCorners[0]); + } + + private void MovePos(ref Vector2 pos, Vector2 size) + { + // 注意 所有的rect都是左下角为基准 + switch (this.layoutType) + { + case ItemLayoutType.Vertical: + // 垂直方向 向下移动 + pos.y -= size.y; + break; + case ItemLayoutType.Horizontal: + // 水平方向 向右移动 + pos.x += size.x; + break; + case ItemLayoutType.VerticalThenHorizontal: + pos.y -= size.y; + if (pos.y <= -this.refRect.height) + { + pos.y = 0; + pos.x += size.x; + } + + break; + case ItemLayoutType.HorizontalThenVertical: + pos.x += size.x; + if (pos.x >= this.refRect.width) + { + pos.x = 0; + pos.y -= size.y; + } + + break; + default: + break; + } + } + + // const int 代替 enum 减少 (int)和(CriticalItemType)转换 + protected static class CriticalItemType + { + public static byte UpToHide = 0; + public static byte DownToHide = 1; + public static byte UpToShow = 2; + public static byte DownToShow = 3; + } + + private class ScrollItemWithRect + { + // scroll item 身上的 RectTransform组件 + public RectTransform item; + + // scroll item 在scrollview中的位置 + public Rect rect; + + // rect 是否需要更新 + public bool rectDirty = true; + } + } +} diff --git a/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs.meta b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs.meta new file mode 100644 index 0000000..1ce3e2f --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollView.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bac7eb1be8f6f40459d388c929d1946f +timeCreated: 1533042733 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs new file mode 100644 index 0000000..0b9a4b1 --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs @@ -0,0 +1,245 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) AillieoTech. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace TapSDK.UI.AillieoTech +{ + using System; + using UnityEngine; + using UnityEngine.EventSystems; + using UnityEngine.Serialization; + + [RequireComponent(typeof(RectTransform))] + [DisallowMultipleComponent] + public class ScrollViewEx : ScrollView + { + [SerializeField] + [FormerlySerializedAs("m_pageSize")] + private int pageSize = 50; + + private int startOffset = 0; + + private Func realItemCountFunc; + + private Vector2 lastPosition; + + private bool reloadFlag = false; + + public override void SetUpdateFunc(Action func) + { + if (func != null) + { + var f = func; + func = (index, rect) => + { + f(index + this.startOffset, rect); + }; + } + + base.SetUpdateFunc(func); + } + + public override void SetItemSizeFunc(Func func) + { + if (func != null) + { + var f = func; + func = (index) => + { + return f(index + this.startOffset); + }; + } + + base.SetItemSizeFunc(func); + } + + public override void SetItemCountFunc(Func func) + { + this.realItemCountFunc = func; + if (func != null) + { + var f = func; + func = () => Mathf.Min(f(), this.pageSize); + } + + base.SetItemCountFunc(func); + } + + public override void OnDrag(PointerEventData eventData) + { + if (this.reloadFlag) + { + this.reloadFlag = false; + this.OnEndDrag(eventData); + this.OnBeginDrag(eventData); + + return; + } + + base.OnDrag(eventData); + } + + protected override void Awake() + { + base.Awake(); + + this.lastPosition = Vector2.up; + this.onValueChanged.AddListener(this.OnValueChanged); + } + + protected override void InternalScrollTo(int index) + { + var count = 0; + if (this.realItemCountFunc != null) + { + count = this.realItemCountFunc(); + } + + index = Mathf.Clamp(index, 0, count - 1); + this.startOffset = Mathf.Clamp(index - (this.pageSize / 2), 0, count - this.itemCountFunc()); + this.UpdateData(true); + base.InternalScrollTo(index - this.startOffset); + } + + private void OnValueChanged(Vector2 position) + { + int toShow; + int critical; + bool downward; + int pin; + + Vector2 delta = position - this.lastPosition; + this.lastPosition = position; + + this.reloadFlag = false; + + if (((int)this.layoutType & flagScrollDirection) == 1) + { + // 垂直滚动 只计算y向 + if (delta.y < 0) + { + // 向上 + toShow = this.criticalItemIndex[CriticalItemType.DownToShow]; + critical = this.pageSize - 1; + if (toShow < critical) + { + return; + } + + pin = critical - 1; + downward = false; + } + else if (delta.y > 0) + { + // 向下 + toShow = this.criticalItemIndex[CriticalItemType.UpToShow]; + critical = 0; + if (toShow > critical) + { + return; + } + + pin = critical + 1; + downward = true; + } + else + { + return; + } + } + else + { + // = 0 + // 水平滚动 只计算x向 + if (delta.x > 0) + { + // 向右 + toShow = this.criticalItemIndex[CriticalItemType.UpToShow]; + critical = 0; + if (toShow > critical) + { + return; + } + + pin = critical + 1; + downward = true; + } + else if (delta.x < 0) + { + // 向左 + toShow = this.criticalItemIndex[CriticalItemType.DownToShow]; + critical = this.pageSize - 1; + if (toShow < critical) + { + return; + } + + pin = critical - 1; + downward = false; + } + else + { + return; + } + } + + // 该翻页了 翻半页吧 + var old = this.startOffset; + if (downward) + { + this.startOffset -= this.pageSize / 2; + } + else + { + this.startOffset += this.pageSize / 2; + } + + var realDataCount = 0; + if (this.realItemCountFunc != null) + { + realDataCount = this.realItemCountFunc(); + } + + this.startOffset = Mathf.Clamp(this.startOffset, 0, Mathf.Max(realDataCount - this.pageSize, 0)); + + if (old != this.startOffset) + { + this.reloadFlag = true; + + // 记录 原先的速度 + Vector2 oldVelocity = this.velocity; + + // 计算 pin元素的世界坐标 + Rect rect = this.GetItemLocalRect(pin); + + Vector2 oldWorld = this.content.TransformPoint(rect.position); + var dataCount = 0; + if (this.itemCountFunc != null) + { + dataCount = this.itemCountFunc(); + } + + if (dataCount > 0) + { + this.EnsureItemRect(0); + if (dataCount > 1) + { + this.EnsureItemRect(dataCount - 1); + } + } + + // 根据 pin元素的世界坐标 计算出content的position + var pin2 = pin + old - this.startOffset; + Rect rect2 = this.GetItemLocalRect(pin2); + Vector2 newWorld = this.content.TransformPoint(rect2.position); + Vector2 deltaWorld = newWorld - oldWorld; + Vector2 deltaLocal = this.content.InverseTransformVector(deltaWorld); + this.SetContentAnchoredPosition(this.content.anchoredPosition - deltaLocal); + this.UpdateData(true); + this.velocity = oldVelocity; + } + } + } +} diff --git a/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs.meta b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs.meta new file mode 100644 index 0000000..1e8275d --- /dev/null +++ b/Runtime/Internal/UI/ScrollViewEx/ScrollView/ScrollViewEx.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 17ff80d59e3504ef385ee40177f0b4a4 +timeCreated: 1533042733 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils.meta b/Runtime/Internal/Utils.meta new file mode 100644 index 0000000..faf0067 --- /dev/null +++ b/Runtime/Internal/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 49cacbc5313da4067862ab3b83239ae5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils/BridgeUtils.cs b/Runtime/Internal/Utils/BridgeUtils.cs new file mode 100644 index 0000000..66ea1ee --- /dev/null +++ b/Runtime/Internal/Utils/BridgeUtils.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace TapSDK.Core.Internal.Utils { + public static class BridgeUtils { + public static bool IsSupportMobilePlatform => Application.platform == RuntimePlatform.Android || + Application.platform == RuntimePlatform.IPhonePlayer; + + public static bool IsSupportStandalonePlatform => Application.platform == RuntimePlatform.OSXPlayer || + Application.platform == RuntimePlatform.WindowsPlayer || + Application.platform == RuntimePlatform.LinuxPlayer; + + public static object CreateBridgeImplementation(Type interfaceType, string startWith) { + // 跳过初始化直接使用 TapLoom会在子线程被TapSDK.Core.BridgeCallback.Invoke 初始化 + TapLoom.Initialize(); + Type bridgeImplementationType = AppDomain.CurrentDomain.GetAssemblies() + .Where(asssembly => asssembly.GetName().FullName.StartsWith(startWith)) + .SelectMany(assembly => assembly.GetTypes()) + .SingleOrDefault(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass); + if (bridgeImplementationType == null){ + Debug.LogWarningFormat( + $"[TapTap] TapSDK Can't find bridge implementation for {interfaceType} on platform {Application.platform}."); + return null; + } + return Activator.CreateInstance(bridgeImplementationType); + } + } +} diff --git a/Runtime/Internal/Utils/BridgeUtils.cs.meta b/Runtime/Internal/Utils/BridgeUtils.cs.meta new file mode 100644 index 0000000..a5492eb --- /dev/null +++ b/Runtime/Internal/Utils/BridgeUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56e7026e809cb4a4ead9bf6479d212a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils/ImageUtils.cs b/Runtime/Internal/Utils/ImageUtils.cs new file mode 100644 index 0000000..8c52af3 --- /dev/null +++ b/Runtime/Internal/Utils/ImageUtils.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Security.Cryptography; +using System.Text; +using System.IO; +using UnityEngine; +using UnityEngine.Networking; + +namespace TapSDK.Core.Internal.Utils { + public class ImageUtils { + private readonly static string CacheDirName = "tap-cache"; + + private readonly static MD5 md5 = MD5.Create(); + + private readonly static Dictionary> cachedTextures = new Dictionary>(); + + public static async Task LoadImage(string url, int timeout = 30, bool useMemoryCache = true) { + if (string.IsNullOrEmpty(url)) { + TapLogger.Warn(string.Format($"ImageUtils Fetch image is null! url is null or empty!")); + return null; + } + + if (cachedTextures.TryGetValue(url, out WeakReference refTex) && + refTex.TryGetTarget(out Texture tex)) { + // 从内存加载 + return tex; + } else { + try { + // 从本地缓存加载 + Texture cachedImage = await LoadCachedImaged(url, timeout); + + if (useMemoryCache) { + cachedTextures[url] = new WeakReference(cachedImage); + } + + return cachedImage; + } catch (Exception e) { + TapLogger.Warn(e.Message); + try { + // 从网络加载 + Texture2D newTex = await FetchImage(url, timeout); + + if (useMemoryCache) { + cachedTextures[url] = new WeakReference(newTex); + } + + // 缓存到本地 + _ = CacheImage(url, newTex); + + return newTex; + } catch (Exception ex) { + TapLogger.Warn(ex.Message); + return null; + } + } + } + } + + public static async Task FetchImage(string url, int timeout = 30) { + using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url)) { + request.timeout = timeout; + UnityWebRequestAsyncOperation operation = request.SendWebRequest(); + while (!operation.isDone) { + await Task.Delay(30); + } + + if (request.isNetworkError || request.isHttpError) { + throw new Exception("Fetch image error."); + } else { + Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler)?.texture; + if (texture == null) { + TapLogger.Warn($"ImageUtils Fetch image is null! url: {url}"); + } + return texture; + } + } + } + + static async Task LoadCachedImaged(string url, int timeout = 30) { + string cachedImagePath = GetCachedPath(url); + if (!File.Exists(cachedImagePath)) { + throw new Exception("No cached image."); + } + string cachedImageUrl = $"file://{cachedImagePath}"; + using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(cachedImageUrl)) { + request.timeout = timeout; + UnityWebRequestAsyncOperation operation = request.SendWebRequest(); + while (!operation.isDone) { + await Task.Delay(30); + } + + if (request.isNetworkError || request.isHttpError) { + RemoveCachedImage(cachedImagePath); + throw new Exception("Load cache image error."); + } else { + var texture = ((DownloadHandlerTexture)request.downloadHandler)?.texture; + if (texture == null) { + RemoveCachedImage(cachedImagePath); + throw new Exception("Cached image is invalid."); + } + return texture; + } + } + } + + static async Task CacheImage(string url, Texture2D tex) { + string cacheImagePath = GetCachedPath(url); + // 写入缓存 + byte[] imageData = tex.EncodeToPNG(); + using (FileStream fileStream = new FileStream(cacheImagePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) { + await fileStream.WriteAsync(imageData, 0, imageData.Length); + } + } + + static void RemoveCachedImage(string cachedImagePath) { + try { + File.Delete(cachedImagePath); + } finally { + + } + } + + static string ToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; i++) { + sb.Append(bytes[i].ToString("x2")); + } + return sb.ToString(); + } + + static string GetCachedPath(string url) { + string cachedHashName = ToHex(md5.ComputeHash(Encoding.UTF8.GetBytes(url))); + return Path.Combine(CacheDirPath, cachedHashName); + } + + static string CacheDirPath { + get { + string path = Path.Combine(Application.persistentDataPath, CacheDirName); + if (!Directory.Exists(path)) { + Directory.CreateDirectory(path); + } + return path; + } + } + } +} diff --git a/Runtime/Internal/Utils/ImageUtils.cs.meta b/Runtime/Internal/Utils/ImageUtils.cs.meta new file mode 100644 index 0000000..62ce190 --- /dev/null +++ b/Runtime/Internal/Utils/ImageUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82045c1fa54ce4e6ab3e286669044f15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils/TapLoom.cs b/Runtime/Internal/Utils/TapLoom.cs new file mode 100644 index 0000000..de647f3 --- /dev/null +++ b/Runtime/Internal/Utils/TapLoom.cs @@ -0,0 +1,155 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; +using System.Threading; +using System.Linq; + +namespace TapSDK.Core.Internal.Utils +{ + public class TapLoom : MonoBehaviour + { + public static int maxThreads = 8; + static int numThreads; + + private static TapLoom _current; + private int _count; + public static TapLoom Current + { + get + { + Initialize(); + return _current; + } + } + + void Awake() + { + _current = this; + initialized = true; + } + + static bool initialized; + + public static void Initialize() + { + if (!initialized) + { + + if (!Application.isPlaying) + return; + initialized = true; + var g = new GameObject("Loom"); + DontDestroyOnLoad(g); + _current = g.AddComponent(); + } + + } + + private List _actions = new List(); + public struct DelayedQueueItem + { + public float time; + public Action action; + } + private List _delayed = new List(); + + List _currentDelayed = new List(); + + public static void QueueOnMainThread(Action action) + { + QueueOnMainThread(action, 0f); + } + public static void QueueOnMainThread(Action action, float time) + { + if (time != 0) + { + lock (Current._delayed) + { + Current._delayed.Add(new DelayedQueueItem { time = Time.time, action = action }); + } + } + else + { + lock (Current._actions) + { + Current._actions.Add(action); + } + } + } + + public static Thread RunAsync(Action a) + { + Initialize(); + while (numThreads >= maxThreads) + { + Thread.Sleep(1); + } + Interlocked.Increment(ref numThreads); + ThreadPool.QueueUserWorkItem(RunAction, a); + return null; + } + + private static void RunAction(object action) + { + try + { + ((Action)action)(); + } + catch + { + } + finally + { + Interlocked.Decrement(ref numThreads); + } + + } + + + void OnDisable() + { + if (_current == this) + { + + _current = null; + } + } + + + + // Use this for initialization + void Start() + { + + } + + List _currentActions = new List(); + + // Update is called once per frame + void Update() + { + lock (_actions) + { + _currentActions.Clear(); + _currentActions.AddRange(_actions); + _actions.Clear(); + } + foreach (var a in _currentActions) + { + a(); + } + lock (_delayed) + { + _currentDelayed.Clear(); + _currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time)); + foreach (var item in _currentDelayed) + _delayed.Remove(item); + } + foreach (var delayed in _currentDelayed) + { + delayed.action(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/Utils/TapLoom.cs.meta b/Runtime/Internal/Utils/TapLoom.cs.meta new file mode 100644 index 0000000..5ff826a --- /dev/null +++ b/Runtime/Internal/Utils/TapLoom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b2d946237d1b489c8e6f14fd553878e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils/TapMessage.cs b/Runtime/Internal/Utils/TapMessage.cs new file mode 100755 index 0000000..5f4a32b --- /dev/null +++ b/Runtime/Internal/Utils/TapMessage.cs @@ -0,0 +1,69 @@ +using System; +using UnityEngine; +using UnityEngine.UI; +public class TapMessage : MonoBehaviour { + + public enum Time + { + threeSecond, + twoSecond, + oneSecond + }; + public enum Position + { + top, + bottom + }; + public static void ShowMessage ( string msg, TapMessage.Position position, TapMessage.Time time ) + { + + //Load message prefab from resources folder + GameObject messagePrefab = Resources.Load ( "TapMessage" ) as GameObject; + //Get container object of message + GameObject containerObject = messagePrefab.gameObject.transform.GetChild ( 0 ).gameObject; + //Get text object + GameObject textObject = containerObject.gameObject.transform.GetChild ( 0 ).GetChild ( 0 ).gameObject; + //Get text property + Text msg_text = textObject.GetComponent ( ); + //Set message to text ui + msg_text.text = msg; + //Set position of container object of message + SetPosition ( containerObject.GetComponent ( ), position ); + //Spawn message object with all changes + GameObject clone = Instantiate ( messagePrefab ); + // Destroy clone of message object according to the time + RemoveClone ( clone, time ); + } + + private static void SetPosition ( RectTransform rectTransform, Position position ) + { + if (position == Position.top) + { + rectTransform.anchorMin = new Vector2 ( 0.5f, 1f ); + rectTransform.anchorMax = new Vector2 ( 0.5f, 1f ); + rectTransform.anchoredPosition = new Vector3 ( 0.5f, -100f, 0 ); + } + else + { + rectTransform.anchorMin = new Vector2 ( 0.5f, 0 ); + rectTransform.anchorMax = new Vector2 ( 0.5f, 0 ); + rectTransform.anchoredPosition = new Vector3 ( 0.5f, 100f, 0 ); + } + } + + private static void RemoveClone ( GameObject clone, Time time ) + { + if (time == Time.oneSecond) + { + Destroy ( clone.gameObject, 1f ); + } + else if (time == Time.twoSecond) + { + Destroy ( clone.gameObject, 2f ); + } + else + { + Destroy ( clone.gameObject, 3f ); + } + } +} diff --git a/Runtime/Internal/Utils/TapMessage.cs.meta b/Runtime/Internal/Utils/TapMessage.cs.meta new file mode 100755 index 0000000..31c0c49 --- /dev/null +++ b/Runtime/Internal/Utils/TapMessage.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 87a6eaa9efa41844890325132c22595a +timeCreated: 1532842822 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Utils/UrlUtils.cs b/Runtime/Internal/Utils/UrlUtils.cs new file mode 100644 index 0000000..8533166 --- /dev/null +++ b/Runtime/Internal/Utils/UrlUtils.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Collections.Specialized; + +namespace TapSDK.Core.Internal.Utils { + public class UrlUtils { + public static NameValueCollection ParseQueryString(string query) { + NameValueCollection nvc = new NameValueCollection(); + + if (query.StartsWith("?")) { + query = query.Substring(1); + } + + foreach (var param in query.Split('&')) { + string[] pair = param.Split('='); + if (pair.Length == 2) { + string key = Uri.UnescapeDataString(pair[0]); + string value = Uri.UnescapeDataString(pair[1]); + nvc[key] = value; + } + } + + return nvc; + } + + public static string ToQueryString(NameValueCollection nvc) { + var array = (from key in nvc.AllKeys + from value in nvc.GetValues(key) + select $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}" + ).ToArray(); + return string.Join("&", array); + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/Utils/UrlUtils.cs.meta b/Runtime/Internal/Utils/UrlUtils.cs.meta new file mode 100644 index 0000000..a9710df --- /dev/null +++ b/Runtime/Internal/Utils/UrlUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 022260b0620b74f97971e1314da78c69 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public.meta b/Runtime/Public.meta new file mode 100644 index 0000000..6e3cd45 --- /dev/null +++ b/Runtime/Public.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b758418efc1d49e58b2aad6c3a456d4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/DataStorage.cs b/Runtime/Public/DataStorage.cs new file mode 100644 index 0000000..cbca05b --- /dev/null +++ b/Runtime/Public/DataStorage.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.NetworkInformation; +using System.Security.Cryptography; +using System.Text; +using UnityEngine; + +namespace TapSDK.Core +{ + public static class DataStorage + { + private static Dictionary dataCache; + private static byte[] Keys = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; + public static void SaveString(string key, string value) + { + SaveStringToCache(key, value); + PlayerPrefs.SetString(key, EncodeString(value)); + } + + public static string LoadString(string key) + { + string value = LoadStringFromCache(key); + if (!string.IsNullOrEmpty(value)) + { + return value; + } + value = PlayerPrefs.HasKey(key) ? DecodeString(PlayerPrefs.GetString(key)) : null; + if (value != null) + { + SaveStringToCache(key, value); + } + return value; + } + + private static void SaveStringToCache(string key, string value) + { + if (dataCache == null) + { + dataCache = new Dictionary(); + } + if (dataCache.ContainsKey(key)) + { + dataCache[key] = value; + } + else + { + dataCache.Add(key, value); + } + } + + private static string LoadStringFromCache(string key) + { + if (dataCache == null) + { + dataCache = new Dictionary(); + } + return dataCache.ContainsKey(key) ? dataCache[key] : null; + } + + private static string EncodeString(string encryptString) + { + if (Platform.IsAndroid() || Platform.IsIOS()) + { + return encryptString; + } + try + { + byte[] rgbKey = Encoding.UTF8.GetBytes(GetMacAddress().Substring(0, 8)); + byte[] rgbIV = Keys; + byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); + DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider(); + MemoryStream mStream = new MemoryStream(); + CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + cStream.Close(); + return Convert.ToBase64String(mStream.ToArray()); + } + catch + { + return encryptString; + } + } + + private static string DecodeString(string decryptString) + { + if (Platform.IsAndroid() || Platform.IsIOS()) + { + return decryptString; + } + try + { + byte[] rgbKey = Encoding.UTF8.GetBytes(GetMacAddress().Substring(0, 8)); + byte[] rgbIV = Keys; + byte[] inputByteArray = Convert.FromBase64String(decryptString); + DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider(); + MemoryStream mStream = new MemoryStream(); + CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + cStream.Close(); + return Encoding.UTF8.GetString(mStream.ToArray()); + } + catch (Exception e) + { + Debug.Log(e.Message); + return decryptString; + } + } + + private static string GetMacAddress() + { + string physicalAddress = "FFFFFFFFFFFF"; + if (Platform.IsAndroid() || Platform.IsIOS()) + { + return physicalAddress; + } + NetworkInterface[] nice = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (NetworkInterface adaper in nice) + { + if (adaper.Description == "en0") + { + physicalAddress = adaper.GetPhysicalAddress().ToString(); + break; + } + else + { + physicalAddress = adaper.GetPhysicalAddress().ToString(); + + if (physicalAddress != "") + { + break; + }; + } + } + return physicalAddress; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/DataStorage.cs.meta b/Runtime/Public/DataStorage.cs.meta new file mode 100644 index 0000000..93b6374 --- /dev/null +++ b/Runtime/Public/DataStorage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32c169d8c12eb4e9598b83c11ad84f48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/ITapPropertiesProxy.cs b/Runtime/Public/ITapPropertiesProxy.cs new file mode 100644 index 0000000..fc18e1f --- /dev/null +++ b/Runtime/Public/ITapPropertiesProxy.cs @@ -0,0 +1,6 @@ + +namespace TapSDK.Core { + public interface ITapPropertiesProxy { + string GetProperties(); + } +} diff --git a/Runtime/Public/ITapPropertiesProxy.cs.meta b/Runtime/Public/ITapPropertiesProxy.cs.meta new file mode 100644 index 0000000..c17e686 --- /dev/null +++ b/Runtime/Public/ITapPropertiesProxy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c03335ad3e42d4092aa308d6503ce66a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/Json.cs b/Runtime/Public/Json.cs new file mode 100644 index 0000000..6c4ad3c --- /dev/null +++ b/Runtime/Public/Json.cs @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2013 Calvin Rien + * + * Based on the JSON parser by Patrick van Bergen + * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + * + * Simplified it so that it doesn't throw exceptions + * and can be used in Unity iPhone with maximum code stripping. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Globalization; + +//Serializer,Serialize + +namespace TapSDK.Core +{ + // Example usage: + // + // using UnityEngine; + // using System.Collections; + // using System.Collections.Generic; + // using MiniJSON; + // + // public class MiniJSONTest : MonoBehaviour { + // void Start () { + // var jsonString = "{ \"array\": [1.44,2,3], " + + // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + + // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + + // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + + // "\"int\": 65536, " + + // "\"float\": 3.1415926, " + + // "\"bool\": true, " + + // "\"null\": null }"; + // + // var dict = Json.Deserialize(jsonString) as Dictionary; + // + // Debug.Log("deserialized: " + dict.GetType()); + // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); + // Debug.Log("dict['string']: " + (string) dict["string"]); + // Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles + // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs + // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); + // + // var str = Json.Serialize(dict); + // + // Debug.Log("serialized: " + str); + // } + // } + + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. + /// All numbers are parsed to doubles. + /// + public static class Json + { + + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false + public static object Deserialize(string json) + { + // save the string for debug information + if (json == null) + { + return null; + } + + return Parser.Parse(json); + } + + sealed class Parser : IDisposable + { + const string WORD_BREAK = "{}[],:\""; + + public static bool IsWordBreak(char c) + { + return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; + } + + enum TOKEN + { + NONE, + CURLY_OPEN, + CURLY_CLOSE, + SQUARED_OPEN, + SQUARED_CLOSE, + COLON, + COMMA, + STRING, + NUMBER, + TRUE, + FALSE, + NULL + }; + + StringReader json; + + Parser(string jsonString) + { + json = new StringReader(jsonString); + } + + public static object Parse(string jsonString) + { + using (var instance = new Parser(jsonString)) + { + return instance.ParseValue(); + } + } + + public void Dispose() + { + json.Dispose(); + json = null; + } + + Dictionary ParseObject() + { + Dictionary table = new Dictionary(); + + // ditch opening brace + json.Read(); + + // { + while (true) + { + switch (NextToken) + { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) + { + return null; + } + + // : + if (NextToken != TOKEN.COLON) + { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; + } + } + } + + List ParseArray() + { + List array = new List(); + + // ditch opening bracket + json.Read(); + + // [ + var parsing = true; + while (parsing) + { + TOKEN nextToken = NextToken; + + switch (nextToken) + { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; + } + } + + return array; + } + + object ParseValue() + { + TOKEN nextToken = NextToken; + return ParseByToken(nextToken); + } + + object ParseByToken(TOKEN token) + { + switch (token) + { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; + } + } + + string ParseString() + { + StringBuilder s = new StringBuilder(); + char c; + + // ditch opening quote + json.Read(); + + bool parsing = true; + while (parsing) + { + + if (json.Peek() == -1) + { + parsing = false; + break; + } + + c = NextChar; + switch (c) + { + case '"': + parsing = false; + break; + case '\\': + if (json.Peek() == -1) + { + parsing = false; + break; + } + + c = NextChar; + switch (c) + { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new char[4]; + + for (int i = 0; i < 4; i++) + { + hex[i] = NextChar; + } + + s.Append((char)Convert.ToInt32(new string(hex), 16)); + break; + } + break; + default: + s.Append(c); + break; + } + } + + return s.ToString(); + } + + object ParseNumber() + { + string number = NextWord; + + if (number.IndexOf('.') == -1) + { + long parsedInt; + Int64.TryParse(number, NumberStyles.Number, CultureInfo.InvariantCulture, out parsedInt); + return parsedInt; + } + + double parsedDouble; + Double.TryParse(number, NumberStyles.Number, CultureInfo.InvariantCulture, out parsedDouble); + return parsedDouble; + } + + void EatWhitespace() + { + while (Char.IsWhiteSpace(PeekChar)) + { + json.Read(); + + if (json.Peek() == -1) + { + break; + } + } + } + + char PeekChar + { + get + { + return Convert.ToChar(json.Peek()); + } + } + + char NextChar + { + get + { + return Convert.ToChar(json.Read()); + } + } + + string NextWord + { + get + { + StringBuilder word = new StringBuilder(); + + while (!IsWordBreak(PeekChar)) + { + word.Append(NextChar); + + if (json.Peek() == -1) + { + break; + } + } + + return word.ToString(); + } + } + + TOKEN NextToken + { + get + { + EatWhitespace(); + + if (json.Peek() == -1) + { + return TOKEN.NONE; + } + + switch (PeekChar) + { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; + } + + switch (NextWord) + { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; + } + + return TOKEN.NONE; + } + } + } + + /// + /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string + /// + /// A Dictionary<string, object> / List<object> + /// A JSON encoded string, or null if object 'json' is not serializable + public static string Serialize(object obj) + { + return Serializer.Serialize(obj); + } + + sealed class Serializer + { + StringBuilder builder; + + Serializer() + { + builder = new StringBuilder(); + } + + public static string Serialize(object obj) + { + var instance = new Serializer(); + + instance.SerializeValue(obj); + + return instance.builder.ToString(); + } + + void SerializeValue(object value) + { + IList asList; + IDictionary asDict; + string asStr; + + if (value == null) + { + builder.Append("null"); + } + else if ((asStr = value as string) != null) + { + SerializeString(asStr); + } + else if (value is bool) + { + builder.Append((bool)value ? "true" : "false"); + } + else if ((asList = value as IList) != null) + { + SerializeArray(asList); + } + else if ((asDict = value as IDictionary) != null) + { + SerializeObject(asDict); + } + else if (value is char) + { + SerializeString(new string((char)value, 1)); + } + else + { + SerializeOther(value); + } + } + + void SerializeObject(IDictionary obj) + { + bool first = true; + + builder.Append('{'); + + foreach (object e in obj.Keys) + { + if (!first) + { + builder.Append(','); + } + + SerializeString(e.ToString()); + builder.Append(':'); + + SerializeValue(obj[e]); + + first = false; + } + + builder.Append('}'); + } + + void SerializeArray(IList anArray) + { + builder.Append('['); + + bool first = true; + + foreach (object obj in anArray) + { + if (!first) + { + builder.Append(','); + } + + SerializeValue(obj); + + first = false; + } + + builder.Append(']'); + } + + void SerializeString(string str) + { + builder.Append('\"'); + + char[] charArray = str.ToCharArray(); + foreach (var c in charArray) + { + switch (c) + { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + int codepoint = Convert.ToInt32(c); + if ((codepoint >= 32) && (codepoint <= 126)) + { + builder.Append(c); + } + else + { + builder.Append("\\u"); + builder.Append(codepoint.ToString("x4")); + } + break; + } + } + + builder.Append('\"'); + } + + void SerializeOther(object value) + { + // NOTE: decimals lose precision during serialization. + // They always have, I'm just letting you know. + // Previously floats and doubles lost precision too. + if (value is float) + { + builder.Append(((float)value).ToString("R", CultureInfo.InvariantCulture)); + } + else if (value is int + || value is uint + || value is long + || value is sbyte + || value is byte + || value is short + || value is ushort + || value is ulong) + { + builder.Append(value); + } + else if (value is double + || value is decimal) + { + builder.Append(Convert.ToDouble(value).ToString("R", CultureInfo.InvariantCulture)); + } + else + { + SerializeString(value.ToString()); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/Public/Json.cs.meta b/Runtime/Public/Json.cs.meta new file mode 100644 index 0000000..f961554 --- /dev/null +++ b/Runtime/Public/Json.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03626f52014bc4f14b549e8bb318ca74 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/Log.meta b/Runtime/Public/Log.meta new file mode 100644 index 0000000..3383d75 --- /dev/null +++ b/Runtime/Public/Log.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 50f5141d913874984bd6a97cace57cbb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/Log/TapLogLevel.cs b/Runtime/Public/Log/TapLogLevel.cs new file mode 100644 index 0000000..1f32c30 --- /dev/null +++ b/Runtime/Public/Log/TapLogLevel.cs @@ -0,0 +1,7 @@ +namespace TapSDK.Core { + public enum TapLogLevel { + Debug, + Warn, + Error, + } +} diff --git a/Runtime/Public/Log/TapLogLevel.cs.meta b/Runtime/Public/Log/TapLogLevel.cs.meta new file mode 100644 index 0000000..2f326d3 --- /dev/null +++ b/Runtime/Public/Log/TapLogLevel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1456af90a56064fe9afe2ccd9692f261 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/Log/TapLogger.cs b/Runtime/Public/Log/TapLogger.cs new file mode 100644 index 0000000..a5ee6c7 --- /dev/null +++ b/Runtime/Public/Log/TapLogger.cs @@ -0,0 +1,48 @@ +using System; +using System.Text; + +namespace TapSDK.Core { + public class TapLogger { + /// + /// Configures the logger. + /// + /// The log delegate. + public static Action LogDelegate { + get; set; + } + + public static void Debug(string log) { + LogDelegate?.Invoke(TapLogLevel.Debug, log); + } + + public static void Debug(string format, params object[] args) { + LogDelegate?.Invoke(TapLogLevel.Debug, string.Format(format, args)); + } + + public static void Warn(string log) { + LogDelegate?.Invoke(TapLogLevel.Warn, log); + } + + public static void Warn(string format, params object[] args) { + LogDelegate?.Invoke(TapLogLevel.Warn, string.Format(format, args)); + } + + public static void Error(string log) { + LogDelegate?.Invoke(TapLogLevel.Error, log); + } + + public static void Error(string format, params object[] args) { + LogDelegate?.Invoke(TapLogLevel.Error, string.Format(format, args)); + } + + public static void Error(Exception e) { + StringBuilder sb = new StringBuilder(); + sb.Append(e.GetType()); + sb.Append("\n"); + sb.Append(e.Message); + sb.Append("\n"); + sb.Append(e.StackTrace); + Error(sb.ToString()); + } + } +} diff --git a/Runtime/Public/Log/TapLogger.cs.meta b/Runtime/Public/Log/TapLogger.cs.meta new file mode 100644 index 0000000..64f11f2 --- /dev/null +++ b/Runtime/Public/Log/TapLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 256e6b07da183466a9621d24df1e3ae7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/Platform.cs b/Runtime/Public/Platform.cs new file mode 100644 index 0000000..6e4eb91 --- /dev/null +++ b/Runtime/Public/Platform.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +namespace TapSDK.Core +{ + public class Platform + { + public static bool IsAndroid() + { + return Application.platform == RuntimePlatform.Android; + } + + public static bool IsIOS() + { + return Application.platform == RuntimePlatform.IPhonePlayer; + } + + public static bool IsWin32() + { + return Application.platform == RuntimePlatform.WindowsPlayer; + } + + public static bool IsMacOS() + { + return Application.platform == RuntimePlatform.OSXPlayer; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/Platform.cs.meta b/Runtime/Public/Platform.cs.meta new file mode 100644 index 0000000..5e39876 --- /dev/null +++ b/Runtime/Public/Platform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef69730c212ed4cc9b4f39d224e60de5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/RegionType.cs b/Runtime/Public/RegionType.cs new file mode 100644 index 0000000..7c98335 --- /dev/null +++ b/Runtime/Public/RegionType.cs @@ -0,0 +1,8 @@ +namespace TapSDK.Core +{ + public enum RegionType : int + { + CN = 0, + IO = 1 + } +} \ No newline at end of file diff --git a/Runtime/Public/RegionType.cs.meta b/Runtime/Public/RegionType.cs.meta new file mode 100644 index 0000000..7e60f9b --- /dev/null +++ b/Runtime/Public/RegionType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77cbd92bb921740e7b83329a483a1221 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/SafeDictionary.cs b/Runtime/Public/SafeDictionary.cs new file mode 100644 index 0000000..90a18f3 --- /dev/null +++ b/Runtime/Public/SafeDictionary.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace TapSDK.Core +{ + public static class SafeDictionary + { + public static T GetValue (Dictionary dic, string key, T defaultVal = default(T)) + { + if (dic == null || dic.Keys.Count == 0) return default(T); + if (!dic.TryGetValue(key, out var outputValue)) + return defaultVal; + if(typeof(T) == typeof(int)){ + return (T)(object)int.Parse(outputValue.ToString()); + } + if(typeof(T) == typeof(double)){ + return (T)(object)double.Parse(outputValue.ToString()); + } + if(typeof(T) == typeof(long)){ + return (T)(object)long.Parse(outputValue.ToString()); + } + if(typeof(T) == typeof(bool)){ + return (T)outputValue; + } + return (T) outputValue; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/SafeDictionary.cs.meta b/Runtime/Public/SafeDictionary.cs.meta new file mode 100644 index 0000000..7f0227c --- /dev/null +++ b/Runtime/Public/SafeDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c5d3a73d3e1d4bf890c3a0b14b560fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapError.cs b/Runtime/Public/TapError.cs new file mode 100644 index 0000000..8cc4c70 --- /dev/null +++ b/Runtime/Public/TapError.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace TapSDK.Core +{ + public class TapError + { + public int code; + + public string errorDescription; + + public TapError(string json) + { + if (string.IsNullOrEmpty(json)) + { + return; + } + + var dic = Json.Deserialize(json) as Dictionary; + code = SafeDictionary.GetValue(dic, "code"); + errorDescription = SafeDictionary.GetValue(dic, "error_description"); + } + + public TapError() + { + } + + public static TapError SafeConstructorTapError(string json) + { + return string.IsNullOrEmpty(json) ? null : new TapError(json); + } + + public static TapError UndefinedError() + { + return new TapError(TapErrorCode.ERROR_CODE_UNDEFINED, "UnKnown Error"); + } + + public static TapError LoginCancelError() + { + return new TapError(TapErrorCode.ERROR_CODE_LOGIN_CANCEL, "Login Cancel"); + } + + public TapError(int code, string errorDescription) + { + this.code = code; + this.errorDescription = errorDescription; + } + + private static TapErrorCode ParseCode(int parseCode) + { + return Enum.IsDefined(typeof(TapErrorCode), parseCode) + ? (TapErrorCode) Enum.ToObject(typeof(TapErrorCode), parseCode) + : TapErrorCode.ERROR_CODE_UNDEFINED; + } + + public TapError(TapErrorCode code, string errorDescription) + { + this.code = (int) code; + this.errorDescription = errorDescription; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/TapError.cs.meta b/Runtime/Public/TapError.cs.meta new file mode 100644 index 0000000..d1f5b16 --- /dev/null +++ b/Runtime/Public/TapError.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c93fd38e60af46b78131cf90dddff5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapErrorCode.cs b/Runtime/Public/TapErrorCode.cs new file mode 100644 index 0000000..3146746 --- /dev/null +++ b/Runtime/Public/TapErrorCode.cs @@ -0,0 +1,44 @@ +namespace TapSDK.Core +{ + public enum TapErrorCode + { + /* + * 未知错误 + */ + ERROR_CODE_UNDEFINED = 80000, + + /** + * SDK 未初始化 + */ + ERROR_CODE_UNINITIALIZED = 80001, + + /** + * 绑定取消 + */ + ERROR_CODE_BIND_CANCEL = 80002, + /** + * 绑定错误 + */ + ERROR_CODE_BIND_ERROR = 80003, + + /** + * 登陆错误 + */ + ERROR_CODE_LOGOUT_INVALID_LOGIN_STATE = 80004, + + /** + * 登陆被踢出 + */ + ERROR_CODE_LOGOUT_KICKED = 80007, + + /** + * 桥接回调错误 + */ + ERROR_CODE_BRIDGE_EXECUTE = 80080, + + /** + * 登录取消 + */ + ERROR_CODE_LOGIN_CANCEL = 80081 + } +} \ No newline at end of file diff --git a/Runtime/Public/TapErrorCode.cs.meta b/Runtime/Public/TapErrorCode.cs.meta new file mode 100644 index 0000000..0de47e2 --- /dev/null +++ b/Runtime/Public/TapErrorCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28bd88a8759b74f55a9e42d04f7aacf3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapException.cs b/Runtime/Public/TapException.cs new file mode 100644 index 0000000..c37cbaa --- /dev/null +++ b/Runtime/Public/TapException.cs @@ -0,0 +1,24 @@ +using System; + +namespace TapSDK.Core +{ + public class TapException : Exception + { + public int code; + + public int Code { + get => code; + set { + code = value; + } + } + + public string message; + + public TapException(int code, string message) : base(message) + { + this.code = code; + this.message = message; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/TapException.cs.meta b/Runtime/Public/TapException.cs.meta new file mode 100644 index 0000000..7ef7c6a --- /dev/null +++ b/Runtime/Public/TapException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8258d80e38ee46738be23d42027f05c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapLanguage.cs b/Runtime/Public/TapLanguage.cs new file mode 100644 index 0000000..bc7f01f --- /dev/null +++ b/Runtime/Public/TapLanguage.cs @@ -0,0 +1,21 @@ +namespace TapSDK.Core +{ + public enum TapLanguage + { + AUTO = 0, + ZH_HANS = 1, + EN = 2, + ZH_HANT = 3, + JA = 4, + KO = 5, + TH = 6, + ID = 7, + DE = 8, + ES = 9, + FR = 10, + PT = 11, + RU = 12, + TR = 13, + VI = 14 + } +} \ No newline at end of file diff --git a/Runtime/Public/TapLanguage.cs.meta b/Runtime/Public/TapLanguage.cs.meta new file mode 100644 index 0000000..eb818fe --- /dev/null +++ b/Runtime/Public/TapLanguage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0794c6f67e35c416bae962c1c8ba4872 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapLocalizeManager.cs b/Runtime/Public/TapLocalizeManager.cs new file mode 100644 index 0000000..ce548d6 --- /dev/null +++ b/Runtime/Public/TapLocalizeManager.cs @@ -0,0 +1,128 @@ +using UnityEngine; + +namespace TapSDK.Core +{ + public class TapLocalizeManager + { + private static volatile TapLocalizeManager _instance; + private static readonly object ObjLock = new object(); + + public static TapLocalizeManager Instance + { + get + { + if (_instance != null) return _instance; + lock (ObjLock) + { + if (_instance == null) + { + _instance = new TapLocalizeManager(); + } + } + + return _instance; + } + } + + private TapLanguage _language = TapLanguage.AUTO; + private bool _regionIsCn; + + public static void SetCurrentRegion(bool isCn) + { + Instance._regionIsCn = isCn; + } + + public static void SetCurrentLanguage(TapLanguage language) + { + Instance._language = language; + } + + public static TapLanguage GetCurrentLanguage() + { + if (Instance._language != TapLanguage.AUTO) return Instance._language; + Instance._language = GetSystemLanguage(); + if (Instance._language == TapLanguage.AUTO) + { + Instance._language = Instance._regionIsCn ? TapLanguage.ZH_HANS : TapLanguage.EN; + } + + return Instance._language; + } + + public static string GetCurrentLanguageString() { + TapLanguage lang = GetCurrentLanguage(); + switch (lang) { + case TapLanguage.ZH_HANS: + return "zh_CN"; + case TapLanguage.EN: + return "en_US"; + case TapLanguage.ZH_HANT: + return "zh_TW"; + case TapLanguage.JA: + return "ja_JP"; + case TapLanguage.KO: + return "ko_KR"; + case TapLanguage.TH: + return "th_TH"; + case TapLanguage.ID: + return "id_ID"; + default: + return null; + } + } + + public static string GetCurrentLanguageString2() { + TapLanguage lang = GetCurrentLanguage(); + switch (lang) { + case TapLanguage.ZH_HANS: + return "zh-CN"; + case TapLanguage.EN: + return "en-US"; + case TapLanguage.ZH_HANT: + return "zh-TW"; + case TapLanguage.JA: + return "ja-JP"; + case TapLanguage.KO: + return "ko-KR"; + case TapLanguage.TH: + return "th-TH"; + case TapLanguage.ID: + return "id-ID"; + default: + return null; + } + } + + private static TapLanguage GetSystemLanguage() + { + var lang = TapLanguage.AUTO; + var sysLanguage = Application.systemLanguage; + switch (sysLanguage) + { + case SystemLanguage.ChineseSimplified: + lang = TapLanguage.ZH_HANS; + break; + case SystemLanguage.English: + lang = TapLanguage.EN; + break; + case SystemLanguage.ChineseTraditional: + lang = TapLanguage.ZH_HANT; + break; + case SystemLanguage.Japanese: + lang = TapLanguage.JA; + break; + case SystemLanguage.Korean: + lang = TapLanguage.KO; + break; + case SystemLanguage.Thai: + lang = TapLanguage.TH; + break; + case SystemLanguage.Indonesian: + lang = TapLanguage.ID; + break; + } + + return lang; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/TapLocalizeManager.cs.meta b/Runtime/Public/TapLocalizeManager.cs.meta new file mode 100644 index 0000000..b25ab7b --- /dev/null +++ b/Runtime/Public/TapLocalizeManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 973ae2f9a5cf94b3cb97d5d27ffcae75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapTapEvent.cs b/Runtime/Public/TapTapEvent.cs new file mode 100644 index 0000000..27c7add --- /dev/null +++ b/Runtime/Public/TapTapEvent.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading.Tasks; +using TapSDK.Core.Internal; +using UnityEngine; +using System.Reflection; + +namespace TapSDK.Core { + public class TapTapEvent { + + private static ITapEventPlatform platformWrapper; + + + static TapTapEvent() { + platformWrapper = PlatformTypeUtils.CreatePlatformImplementationObject(typeof(ITapEventPlatform), + "TapSDK.Core") as ITapEventPlatform; + if(platformWrapper == null) { + Debug.LogError("PlatformWrapper is null"); + } + } + + public static void SetUserID(string userID){ + platformWrapper?.SetUserID(userID); + } + + public static void SetUserID(string userID, string properties){ + platformWrapper?.SetUserID(userID,properties); + } + public static void ClearUser(){ + platformWrapper?.ClearUser(); + } + + public static string GetDeviceId(){ + if(platformWrapper != null) { + return platformWrapper?.GetDeviceId(); + } + return ""; + } + + public static void LogEvent(string name, string properties){ + platformWrapper?.LogEvent(name, properties); + } + + public static void DeviceInitialize(string properties){ + platformWrapper?.DeviceInitialize(properties); + } + + public static void DeviceUpdate(string properties){ + platformWrapper?.DeviceUpdate(properties); + } + public static void DeviceAdd(string properties){ + platformWrapper?.DeviceAdd(properties); + } + + public static void UserInitialize(string properties){ + platformWrapper?.UserInitialize(properties); + } + + public static void UserUpdate(string properties){ + platformWrapper?.UserUpdate(properties); + } + + public static void UserAdd(string properties){ + platformWrapper?.UserAdd(properties); + } + + public static void AddCommonProperty(string key, string value){ + platformWrapper?.AddCommonProperty(key, value); + } + + public static void AddCommon(string properties){ + platformWrapper?.AddCommon(properties); + } + + public static void ClearCommonProperty(string key){ + platformWrapper?.ClearCommonProperty(key); + } + public static void ClearCommonProperties(string[] keys){ + platformWrapper?.ClearCommonProperties(keys); + } + + public static void ClearAllCommonProperties(){ + platformWrapper?.ClearAllCommonProperties(); + } + public static void LogPurchasedEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties){ + platformWrapper?.LogChargeEvent(orderID, productName, amount, currencyType, paymentMethod, properties); + } + + public static void RegisterDynamicProperties(Func callback){ + platformWrapper?.RegisterDynamicProperties(callback); + } + + } +} diff --git a/Runtime/Public/TapTapEvent.cs.meta b/Runtime/Public/TapTapEvent.cs.meta new file mode 100644 index 0000000..a2dd04b --- /dev/null +++ b/Runtime/Public/TapTapEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 573a66a7d4c2a49309f0b3e2cef0eecb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapTapSDK.cs b/Runtime/Public/TapTapSDK.cs new file mode 100644 index 0000000..a0bc29e --- /dev/null +++ b/Runtime/Public/TapTapSDK.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading.Tasks; +using System.Linq; +using TapSDK.Core.Internal; +using System.Collections.Generic; + +using UnityEngine; +using System.Reflection; +using TapSDK.Core.Internal.Init; +using TapSDK.Core.Internal.Log; + +namespace TapSDK.Core { + public class TapTapSDK { + public static readonly string Version = "4.4.0"; + + public static string SDKPlatform = "TapSDK-Unity"; + + public static TapTapSdkOptions taptapSdkOptions; + private static ITapCorePlatform platformWrapper; + + private static bool disableDurationStatistics; + + public static bool DisableDurationStatistics { + get => disableDurationStatistics; + set { + disableDurationStatistics = value; + } + } + + static TapTapSDK() { + platformWrapper = PlatformTypeUtils.CreatePlatformImplementationObject(typeof(ITapCorePlatform), + "TapSDK.Core") as ITapCorePlatform; + } + + public static void Init(TapTapSdkOptions coreOption) { + if (coreOption == null) + throw new ArgumentException("[TapSDK] options is null!"); + if (string.IsNullOrEmpty(coreOption.clientId)) + throw new ArgumentException("[TapSDK] clientID is null or empty!"); + TapTapSDK.taptapSdkOptions = coreOption; + TapLog.Enabled = coreOption.enableLog; + platformWrapper?.Init(coreOption); + // 初始化各个模块 + + Type[] initTaskTypes = GetInitTypeList(); + if (initTaskTypes != null) { + List initTasks = new List(); + foreach (Type initTaskType in initTaskTypes) { + initTasks.Add(Activator.CreateInstance(initTaskType) as IInitTask); + } + initTasks = initTasks.OrderBy(task => task.Order).ToList(); + foreach (IInitTask task in initTasks) { + TapLogger.Debug($"Init: {task.GetType().Name}"); + task.Init(coreOption); + } + } + } + + public static void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions){ + if (coreOption == null) + throw new ArgumentException("[TapSDK] options is null!"); + if (string.IsNullOrEmpty(coreOption.clientId)) + throw new ArgumentException("[TapSDK] clientID is null or empty!"); + + TapTapSDK.taptapSdkOptions = coreOption; + TapLog.Enabled = coreOption.enableLog; + platformWrapper?.Init(coreOption,otherOptions); + + Type[] initTaskTypes = GetInitTypeList(); + if (initTaskTypes != null) { + List initTasks = new List(); + foreach (Type initTaskType in initTaskTypes) { + initTasks.Add(Activator.CreateInstance(initTaskType) as IInitTask); + } + initTasks = initTasks.OrderBy(task => task.Order).ToList(); + foreach (IInitTask task in initTasks) { + TapLogger.Debug($"Init: {task.GetType().Name}"); + task.Init(coreOption,otherOptions); + } + } + } + + // UpdateLanguage 方法 + public static void UpdateLanguage(TapTapLanguageType language){ + platformWrapper?.UpdateLanguage(language); + } + + private static Type[] GetInitTypeList(){ + Type interfaceType = typeof(IInitTask); + Type[] initTaskTypes = AppDomain.CurrentDomain.GetAssemblies() + .Where(asssembly => asssembly.GetName().FullName.StartsWith("TapSDK")) + .SelectMany(assembly => assembly.GetTypes()) + .Where(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass) + .ToArray(); + return initTaskTypes; + } + + } +} diff --git a/Runtime/Public/TapTapSDK.cs.meta b/Runtime/Public/TapTapSDK.cs.meta new file mode 100644 index 0000000..3175d31 --- /dev/null +++ b/Runtime/Public/TapTapSDK.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4782af0660aa412ca06e2fa91af5838 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapTapSdkOptions.cs b/Runtime/Public/TapTapSdkOptions.cs new file mode 100644 index 0000000..a907ed2 --- /dev/null +++ b/Runtime/Public/TapTapSdkOptions.cs @@ -0,0 +1,104 @@ +using UnityEngine; +using Newtonsoft.Json; + +namespace TapSDK.Core +{ + public interface TapTapSdkBaseOptions + { + string moduleName { get; } + + AndroidJavaObject androidObject{get;} + } + + public enum TapTapRegionType + { + CN = 0, + Overseas = 1 + } + + + public enum TapTapLanguageType + { + Auto = 0,// 自动 + zh_Hans,// 简体中文 + en,// 英文 + zh_Hant,// 繁体中文 + ja,// 日文 + ko,// 韩文 + th,// 泰文 + id,// 印度尼西亚语 + de,// 德语 + es,// 西班牙语 + fr,// 法语 + pt,// 葡萄牙语 + ru,// 俄罗斯语 + tr,// 土耳其语 + vi// 越南语 + } + + public class TapTapSdkOptions : TapTapSdkBaseOptions + { + /// + /// 客户端 ID,开发者后台获取 + /// + public string clientId; + /// + /// 客户端令牌,开发者后台获取 + /// + public string clientToken; + /// + /// 地区,CN 为国内,Overseas 为海外 + /// + public TapTapRegionType region = TapTapRegionType.CN; + /// + /// 语言,默认为 Auto,默认情况下,国内为 zh_Hans,海外为 en + /// + public TapTapLanguageType preferredLanguage = TapTapLanguageType.Auto; + /// + /// 渠道,如 AppStore、GooglePlay + /// + public string channel = null; + /// + /// 游戏版本号,如果不传则默认读取应用的版本号 + /// + public string gameVersion = null; + /// + /// 初始化时传入的自定义参数,会在初始化时上报到 device_login 事件 + /// + public string propertiesJson = null; + /// + /// CAID,仅国内 iOS + /// + public string caid = null; + /// + /// 是否能够覆盖内置参数,默认为 false + /// + public bool overrideBuiltInParameters = false; + /// + /// 是否开启广告商 ID 收集,默认为 false + /// + public bool enableAdvertiserIDCollection = false; + /// + /// 是否开启自动上报 IAP 事件 + /// + public bool enableAutoIAPEvent = true; + /// + /// OAID证书, 仅 Android,用于上报 OAID 仅 [TapTapRegion.CN] 生效 + /// + public string oaidCert = null; + /// + /// 是否开启日志,Release 版本请设置为 false + /// + public bool enableLog = false; + + [JsonProperty("moduleName")] + private string _moduleName = "TapTapSDKCore"; + [JsonIgnore] + public string moduleName + { + get => _moduleName; + } + + public AndroidJavaObject androidObject => throw new System.NotImplementedException(); + } +} diff --git a/Runtime/Public/TapTapSdkOptions.cs.meta b/Runtime/Public/TapTapSdkOptions.cs.meta new file mode 100644 index 0000000..29224fb --- /dev/null +++ b/Runtime/Public/TapTapSdkOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49658d3bba43c49e2a9b4dbca6f1b2ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/TapSDK.Core.Runtime.asmdef b/Runtime/TapSDK.Core.Runtime.asmdef new file mode 100644 index 0000000..fc60a33 --- /dev/null +++ b/Runtime/TapSDK.Core.Runtime.asmdef @@ -0,0 +1,3 @@ +{ + "name": "TapSDK.Core.Runtime" +} diff --git a/Runtime/TapSDK.Core.Runtime.asmdef.meta b/Runtime/TapSDK.Core.Runtime.asmdef.meta new file mode 100644 index 0000000..caf9f4a --- /dev/null +++ b/Runtime/TapSDK.Core.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7d5ef2062f3704e1ab74aac0e4d5a1a7 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone.meta b/Standalone.meta new file mode 100644 index 0000000..de55049 --- /dev/null +++ b/Standalone.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21630d052faa249cfa7be42de75e1fee +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Editor.meta b/Standalone/Editor.meta new file mode 100644 index 0000000..d63e4aa --- /dev/null +++ b/Standalone/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10883ac51961f49e3aeb26c5723c841e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Editor/TapCoreStandaloneProcessBuild.cs b/Standalone/Editor/TapCoreStandaloneProcessBuild.cs new file mode 100644 index 0000000..18acf0b --- /dev/null +++ b/Standalone/Editor/TapCoreStandaloneProcessBuild.cs @@ -0,0 +1,20 @@ +using System; +using UnityEditor.Build.Reporting; +using TapSDK.Core.Editor; + +namespace TapSDK.Core.Editor { + public class TapCoreStandaloneProcessBuild : SDKLinkProcessBuild { + public override int callbackOrder => 0; + + public override string LinkPath => "TapSDK/Core/link.xml"; + + public override LinkedAssembly[] LinkedAssemblies => new LinkedAssembly[] { + new LinkedAssembly { Fullname = "TapSDK.Core.Runtime" }, + new LinkedAssembly { Fullname = "TapSDK.Core.Standalone.Runtime" } + }; + + public override Func IsTargetPlatform => (report) => { + return BuildTargetUtils.IsSupportStandalone(report.summary.platform); + }; + } +} \ No newline at end of file diff --git a/Standalone/Editor/TapCoreStandaloneProcessBuild.cs.meta b/Standalone/Editor/TapCoreStandaloneProcessBuild.cs.meta new file mode 100644 index 0000000..db9d6ac --- /dev/null +++ b/Standalone/Editor/TapCoreStandaloneProcessBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c9b26133fd2ab4d9aafdfa492b519e84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef b/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef new file mode 100644 index 0000000..e65fb22 --- /dev/null +++ b/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "TapSDK.Core.Standalone.Editor", + "references": [ + "GUID:56f3da7a178484843974054bafe77e73" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef.meta b/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef.meta new file mode 100644 index 0000000..157ea03 --- /dev/null +++ b/Standalone/Editor/TapSDK.Core.Standalone.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 223f7c51738354e2cb8b4cb571f7caab +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins.meta b/Standalone/Plugins.meta new file mode 100644 index 0000000..3cbfd6c --- /dev/null +++ b/Standalone/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 39e555e59069c4dc1995634eb234a4bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS.meta b/Standalone/Plugins/macOS.meta new file mode 100644 index 0000000..060318b --- /dev/null +++ b/Standalone/Plugins/macOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 28bbe390b7a2d4e1d9e6de4d5865fc52 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle.meta b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle.meta new file mode 100644 index 0000000..2af5fc9 --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 3b398345a81614ec48f4f2c0b7b1694a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents.meta b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents.meta new file mode 100644 index 0000000..a8fc4b4 --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 513af68bdf23642ae805764c81d993d7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist new file mode 100644 index 0000000..12767e8 --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist @@ -0,0 +1,46 @@ + + + + + BuildMachineOSBuild + 22D68 + CFBundleDevelopmentRegion + en + CFBundleExecutable + TapDBDeviceInfo + CFBundleIdentifier + com.tap.TapDBDeviceInfo + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + TapDBDeviceInfo + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 14C18 + DTPlatformName + macosx + DTPlatformVersion + 13.1 + DTSDKBuild + 22C55 + DTSDKName + macosx13.1 + DTXcode + 1420 + DTXcodeBuild + 14C18 + LSMinimumSystemVersion + 13.1 + + diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist.meta b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist.meta new file mode 100644 index 0000000..af16891 --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/Info.plist.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 655e2fea5c6fc45079aaf3180c272928 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS.meta b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS.meta new file mode 100644 index 0000000..f051fde --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db80558b4cee94425ba0f080aa28fea1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS/TapDBDeviceInfo b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/MacOS/TapDBDeviceInfo new file mode 100755 index 0000000000000000000000000000000000000000..59c8e6ac28ebcf031c2111921ec9e38638f056d8 GIT binary patch literal 167584 zcmeI*eRNdiod@t|@`9)l5`};uIwIl=FcX0Sx+~6PLQG^#*t}pW#p|7!XOc-~?v3}} z2??<4tQJZ{ccDe}v^^fGJt_#*W3@-EtWi-^wiT=|JyKD(y5d_8?OIWGA^ZE?duJRb zKs@W7?m6A>bIvo*^SjUU{AT8JpBw$>VO~1&#J>n3iabJ`L~XGU0UiOl z#TlxjHc@XBRTKz700Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*#kmjzxv^yv{g z`0t^E|6(N7GpK$sLWouF`Fx}U>EAQRm62|~uRdRUG^)tn zsO+{BON|uf3%L2bZU&Bt<+w#r^hjq^NlB|iQ&V!t_r2cDcbS_Z8)p+1=2n!bX&DKv z-JMYQ`F`l;8|{u~!Tg$1hDD8UQHaTv-DmNYH<@MRZ_cK2xw|G8W;peLgI4iwQ zBRI}q?_SO|?tM+9)ON{qh4~iIdM^Adap8D4xBWEF$-^t$l5yL>vE!^+Hn;n^Oqn!f zoF0qg<@37f_-V@jp7g9Tf3CRsqDj>>Ib&fyZ^oIL{hl1>=cCc1^6~ztqpB$3`i6xI zYU;BaYk)3t$=I(#3^?t9tDTmg+5G2rHqEpX)4VzwnB~TGPYdCtx$H5XYB$AWsQv9T z6w}31slSLS7w&VV<@P^tlKl=84k-`O`jdxT6=EV?dl_{^Z^B zpP5P5yi_?i{W~gl<-Tp>nO%G__48r)A6U7bD(B+QE(U)>e~zbn{LOu)B~$)zB4VgU zpTE|izaqS-;eS_xhA#j{uai=|=G71JNe8>e)fe;(Cbe(@ef9^p@$hCi8z zaOJ;LzMwDGM|~Q!3$Lr9np-zz3~iC4*F~pI^IhUD8~Q#M44LnIC0}*^JMp@X{P(fH z|3-0w?;p|)d18a0|L8fCe=1dW?|9mIHZO8x-X~lpQRVRiJLsjJDhdQ3009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2teSkN1(N?|NY=K!NtMWU`z9&y0w=-8KiTXYrnP4ZYJI3p#zS;sq5dl zpMP||zW;d|x`l^s8+#88(SS3$$sXN7qcI+(%VVR?`2JNi#0i$!?XxuH((IG~O{wqS z8D2YMzCHQv;NWZF{txWegZAqebvN%Axg_|V;L_lAL1pQ)G`p_RK_lWrELiC>e zphx_p=Q<%wOQpk_My%D8MvaWC>7-=}I&dkYR;_LCobkx{B=uRI?oJExjw;X$^^10EG0U`LzJT>-tPrrM9n`8Sr#e4>}j8>J~ zD}~t8R{D2ziAs5fb8%~*L8aC?%Uhl)aj(;1PJ5isSJS&@UZ7K|T(alPM>(PW8p!LT z3jqi~00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fWYtq|7`y|0p;Rmw@tgP^LzB= z!ae4n89ozcKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##An;8DytKH3>Kdve zsd}hpONrYVPiBC%)HAyRNXp7Zk+J3iCZAjNlOVzAq%SdSLp`>b>O)@SGsYPixj&j}In@fP+3VC5UU7{3al(6ej14&4miUWq(JMnCMfOHzx20HW zBq^(epZB2Y*EMO-mhVgZ+iLtRYIjYwvkmLDxb7EU^?j+-&?VtS#88bsKTVTKot`#- zTU3upij72%jMb=?T9|W&w7Sw=GTmVs%X{?Y@s!aM>0XwI_a-AZB>dIsL^9^D*DPtI zWGtat()4LMJ=n6hFp)1}&uwy^-{XunMmlBGGG}PIky4ZX-g$GBIkWlW(u2m|(5D~k zP7lT6O+n?*zOn(@Zue6+yC?bap#4|0fj1(xn`jQl-J;0e*1R`&Q*EQlYiQf;IN&W3 zWz_!~C6JpF)l)u8?e-PiBN1P{ul(*?{O?Yjv+J?cuB|Uz@bZq2D!Q(I;E&(E?%F>@ z?)uT4ecp8q56DsJ-djtr+VoIULuclxofGbv@x?21?;RKVa`h7fQ^S|M|MB<4%(K1W zm5(0O#eyG)wNGE%X-@op<=(rumaH57$xYj?x%PwRhdw=T)`T%-^^Y%Dy#2%(pO^B=|sO5WJBr>m&3t#okZBP*A$dUN~Qy8h|+4L9ES*}&+DFOB=p zkM^HFY00i@FM7Cc);~P>%Y%2^)}%eXchQxf-7$K?kzbuRZpQAlhrhVX_w6%1*Pm4L z!rV9Boi#1B=>0jjHx8Hon~tyk!LQGoc3bKcsXln*Pm{m%=N+y0-?X;&@QvsH?qEma zgSWbo)jKs;AUWfw)UWUu80)D-4>^>SzINp}jz{KMP1QX(?BK6Mnuw9*kX zcd0Pny7|sfl{pwL-VDzW%MiWN`iiqo#qsN->q)G>=xptJ#X6`H(z$2aNg_>!}B|gplbgLJO@!KGeBO*RHTyj|&d*1qN9E)FQAbr# z!u1Uc7u3{eH`V}M*IX_Ds6wrOLU5=QO)=-?s70F20!h`7r$V?0Tx4%f6R`KcPRt(>?y?KGTvZe>f2_ zRHM&dYtU1_OgFkrf32R@VyczUHD5e?)xTI8CS5W8p}cX*$@%By=NIo$I?c}eblExyYRXys=3cKWejbRqt`{JP4iviE*ttj7Yv#2d?jCX{yXuyj{Ntr zzyC&Yg6|*F4S8aNp#SJOlz%E!cJFxFc{VR{W8NoRCQ;?_P0!IwJyjG4KmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0ucCrFYx&_+c$sK=-GUDMbYNNH;eV>c#B^fs3>Z?T8IsYizjWNb9fv6 zqgZUYyF!%n7#+p6uhUt=4WH3)UQtc&8{GW)HP7BZP^`S3oj;1k=`7+HCmyqf&OheGXKWeauBUU28|cj3 zL*+$*FGbNguiO1mdHvJ#`jt-qT$+Eb2z+VJJ;k}^72a2~)gCVyW_;u@<7LMm9~|^d z6c-9{;h6h9B2adxM}+D4XG_T%k7$e0P7HQ;CuLaG+S7Evm(J0)w$#pRb`NsL6NYIu zMmlBG5_D#gnp)jhDElrGrlr!6Oe5B6N~1=`)pXLLwd5nta(6ek9DAwqInR*+A^6NZ zHTL<;_3n9fj_vDolljbO8LcX}SK6_AYPLMXx#+}VhI4UipD(4h#Et3jrG1_56%}vh z1@~Zo_MAE0<6H^kdgwp^0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bcLbp)K> zc`p|$?cZlF7i-*hquaX2{4-xC7v?|!0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bcLKNs-Q(hjO?sE(xSp_(lvZf88TRoRQwax4NPsB*_Cye@Y=<6fkOua^`FQ9N0Q zq6Vs+R992|g%G7js8-VqMQPQRCA65*B+Yb6QmnN1_A1@dFm+8$CagZCr%EV#q%*3d z%=Tuf#oWk{NvWE&!X7iEr6#ms+R}%)9&3_T+RzrO$+Z1$q1uqHnU<L`@Sw=GuTcN&Vj%|R+n2@1Zv2iLml6e!3;W++5aL`n_PTKm#iQt^1l*WH5#txP zET(=e8&JEVAnvC)PIshZ@|pHDUh1QlS1)%KEv5T3B`@ajDO8_yhV6UVkT>2<^QWHR z^U$yuoi~3h#T1sh>BLMYrVLK0cH-PPe@L3oB6e9$uM~4yyYQaPk2e;?KP!kUDCW;l zNp&1mj?bVvhHCzGuQbd^rM0DYp0m%7ex|0x6NYIuMmlBG zDj1>lbg@=97RtWM3dZ=kiK#~HsHft}P)L!zQQ2)NmKsUQD&gmG)30mNpog?C?Qg5` zx2WAU)y{)cuf=u0_^R(qrG_pECnAPw^!aI;OzQOa!QU3uW0GPc(IaCus-+g@oFT2Q zbeBwbn8xxReR(`(^hCOsCE~rw$PEd9bvluZ`Rg@H8Yvk|sFpN+nofU;WN%?2U&Nl< zO^D|6l+RMTeFb+>Yt zzxx*dyA$W^dMveT>kAjWyyK&auB#vT<9DyS_79P}espJ_cU{8+a#Xta*3zpsJ=E0D znR#mGgnMRu@ygtL$A!LJ{lvi3@Fnkm{5>)AY;SnwqX%`d;KyO@(-(J|6Te@%_wKEe z=iYkz4tFZywi~xT+@?SOVSJ$EjXit1 ziW=KW2Uk9_a`~z^x38`1pMKwP;|CXCUa`Kz|J3F=J)XTQCz-!ocFv)SpSND~)4lsP zee2S<*F2`{oeQ4ZcH?a;?pb=y?unZ(81YQcNq5Y7@xFV*znlG9#ihgL|EA+l{d(c< z`0Fq2s;E5Du;t_1e|5#w1JehM(7lVl9WD8(wk`Ed^AETF_v^b>+!gG4^SS0vqDwYT z*fa6LXP + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature/CodeResources.meta b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature/CodeResources.meta new file mode 100644 index 0000000..9cf9084 --- /dev/null +++ b/Standalone/Plugins/macOS/TapDBDeviceInfo.bundle/Contents/_CodeSignature/CodeResources.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 19a051370461741e88722438f6ae81c1 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime.meta b/Standalone/Runtime.meta new file mode 100644 index 0000000..a554c78 --- /dev/null +++ b/Standalone/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f9383e2313414222b398988ccddf3fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal.meta b/Standalone/Runtime/Internal.meta new file mode 100644 index 0000000..bbca9e8 --- /dev/null +++ b/Standalone/Runtime/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dcf82a808dfb24329ab66136da3a0220 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Bean.meta b/Standalone/Runtime/Internal/Bean.meta new file mode 100644 index 0000000..a2e4db4 --- /dev/null +++ b/Standalone/Runtime/Internal/Bean.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db93f492877c64b2f959c3a04debd60f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs b/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs new file mode 100644 index 0000000..ceda615 --- /dev/null +++ b/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TapSDK.Core.Standalone.Internal.Bean +{ + [Serializable] + internal class TapGatekeeper + { + [JsonProperty("switch")] + public TapGatekeeperSwitch Switch { get; set; } = new TapGatekeeperSwitch(); + + [JsonProperty("urls")] + public Dictionary Urls { get; set; } + + [JsonProperty("taptap_app_id")] + public int? TapTapAppId { get; set; } + } + + [Serializable] + internal class TapGatekeeperSwitch + { + [JsonProperty("auto_event")] + public bool AutoEvent { get; set; } = true; + + [JsonProperty("heartbeat")] + public bool Heartbeat { get; set; } = true; + } + + [Serializable] + internal class Url + { + [JsonProperty("webview")] + public string WebView { get; set; } + + [JsonProperty("browser")] + public string Browser { get; set; } + + [JsonProperty("uri")] + public string TapUri { get; set; } + } +} diff --git a/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs.meta b/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs.meta new file mode 100644 index 0000000..63554a3 --- /dev/null +++ b/Standalone/Runtime/Internal/Bean/TapGatekeeper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba3269645153d4a47b23dd75efdeab5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Constants.cs b/Standalone/Runtime/Internal/Constants.cs new file mode 100644 index 0000000..d1563f0 --- /dev/null +++ b/Standalone/Runtime/Internal/Constants.cs @@ -0,0 +1,37 @@ + +namespace TapSDK.Core.Standalone.Internal { + public static class Constants { + public static readonly string EVENT = "event"; + + public static readonly string PROPERTY_INITIALIZE_TYPE = "initialise"; + public static readonly string PROPERTY_UPDATE_TYPE = "update"; + public static readonly string PROPERTY_ADD_TYPE = "add"; + + + public readonly static string SERVER_URL_CN = "https://e.tapdb.net"; + public readonly static string SERVER_URL_IO = "https://e.tapdb.ap-sg.tapapis.com"; + public readonly static string DEVICE_LOGIN = "device_login"; + public readonly static string USER_LOGIN = "user_login"; + + internal static string ClientSettingsFileName = "TapSDKClientSettings.json"; + internal static string ClientSettingsEventKey = "ClientSettingsEventKey"; + + public static string TAPSDK_HOST { + get { + if (TapCoreStandalone.isRnd) { + if (TapCoreStandalone.coreOptions.region == TapTapRegionType.CN) + return "https://tapsdk.api.xdrnd.cn"; + else + return "https://tapsdk.api.xdrnd.com"; + } else { + if (TapCoreStandalone.coreOptions.region == TapTapRegionType.CN) + return "https://tapsdk.tapapis.cn"; + else + return "https://tapsdk.tapapis.com"; + } + } + } + + } + +} diff --git a/Standalone/Runtime/Internal/Constants.cs.meta b/Standalone/Runtime/Internal/Constants.cs.meta new file mode 100644 index 0000000..3e0a3e3 --- /dev/null +++ b/Standalone/Runtime/Internal/Constants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 240dff90393004fc28bd171ef06f3ea1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/DeviceInfo.cs b/Standalone/Runtime/Internal/DeviceInfo.cs new file mode 100644 index 0000000..72f1a40 --- /dev/null +++ b/Standalone/Runtime/Internal/DeviceInfo.cs @@ -0,0 +1,152 @@ +using System; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using UnityEngine; + +namespace TapSDK.Core.Standalone.Internal { + public class DeviceInfo { + +#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX + [DllImport("TapDBDeviceInfo", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GetDeviceLanguage(); +#endif + + public static string GetLanguage() { +#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX + return Marshal.PtrToStringAnsi(GetDeviceLanguage()); +#else + return CultureInfo.CurrentUICulture.IetfLanguageTag; +#endif + } + +#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN + [DllImport("kernel32.dll")] + static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll")] + static extern uint GetProcessTimes(IntPtr processHandle, + out long creationTime, + out long exitTime, + out long kernelTime, + out long userTime); + + static DateTime GetProcessStartTime() + { + IntPtr processHandle = GetCurrentProcess(); + long creationTime; + GetProcessTimes(processHandle, + out creationTime, + out _, + out _, + out _); + + return DateTime.FromFileTime(creationTime); + } +#endif + + //安全组提供的设备识别 ID 算法,用于后续数据串联 + public static string GetLaunchUniqueID() + { +#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN + // 获取当前进程对象 + Process currentProcess = Process.GetCurrentProcess(); + // 获取进程启动时间 + DateTime startTime = GetProcessStartTime(); + return toMd5(startTime.ToFileTime().ToString() + "-" + currentProcess.Id.ToString()); +#else + return ""; +#endif + + } + + public static void GetMacAddress(out string macAddressList, out string firstMacAddress) + { + List mac_addrs = new List(); + + try + { + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface adapter in nics) + { + string physicalAddress = adapter.GetPhysicalAddress().ToString(); + if (string.IsNullOrEmpty(physicalAddress)) + continue; + + physicalAddress = $"\"{physicalAddress}\""; + if (mac_addrs.IndexOf(physicalAddress) == -1) + mac_addrs.Add(physicalAddress); + } + // sort + mac_addrs.Sort(); + } + catch (Exception e) + { + UnityEngine.Debug.Log("GetMacAddress Exception " + e.Message); + } + macAddressList = $"[{string.Join(",", mac_addrs)}]"; + firstMacAddress = mac_addrs.Count > 0 ? mac_addrs[0].Replace("\"", "") : string.Empty; + } + + private static string toMd5(string data) + { + byte[] buffer = System.Text.Encoding.Default.GetBytes(data); + try + { + System.Security.Cryptography.MD5CryptoServiceProvider chk = new System.Security.Cryptography.MD5CryptoServiceProvider(); + byte[] some = chk.ComputeHash(buffer); + string ret = ""; + foreach(byte a in some) + { + if (a < 16) + ret += "0" + a.ToString("X"); + else + ret += a.ToString("X"); + } + return ret.ToLower(); + } + catch + { + throw; + } + } + + public static string RAM + { + get { + return (SystemInfo.systemMemorySize * 1024L * 1024L).ToString(); + } + } + + // 获取系统的 ROM 存储容量,单位为 Byte + public static string ROM + { + get { + string path = Application.persistentDataPath; + if (string.IsNullOrEmpty(path)) + return "0"; + + try + { + System.IO.DriveInfo drive = new System.IO.DriveInfo(path); + return drive.TotalSize.ToString(); + } + catch (Exception e) + { + UnityEngine.Debug.Log("GetROMInByte Exception " + e.Message); + return "0"; + } + } + } + + public static string Local + { + get { + return CultureInfo.CurrentCulture.Name.Replace('-', '_'); + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/DeviceInfo.cs.meta b/Standalone/Runtime/Internal/DeviceInfo.cs.meta new file mode 100644 index 0000000..4096941 --- /dev/null +++ b/Standalone/Runtime/Internal/DeviceInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d118285dce52d4ee38c22f3c1c174569 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/EventSender.cs b/Standalone/Runtime/Internal/EventSender.cs new file mode 100644 index 0000000..825beb5 --- /dev/null +++ b/Standalone/Runtime/Internal/EventSender.cs @@ -0,0 +1,250 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TapSDK.Core.Internal.Log; +using TapSDK.Core.Standalone.Internal.Http; +using UnityEngine; +using UnityEngine.Networking; + +namespace TapSDK.Core.Standalone.Internal +{ + public class EventSender : MonoBehaviour + { + private const string EventFilePath = "events.json"; + + private readonly TapLog log = new TapLog("TapEvent"); + private string persistentDataPath = Application.persistentDataPath; + + private Queue> eventQueue = new Queue>(); + private TapHttp tapHttp = TapHttp + .NewBuilder("TapSDKCore", TapTapSDK.Version) + .Sign(TapHttpSign.CreateNoneSign()) + .Parser(TapHttpParser.CreateEventParser()) + .Build(); + + private const int MaxEvents = 50; + private const int MaxBatchSize = 200; + private const float SendInterval = 15f; + private Timer timer; + private DateTime lastSendTime; + + private string domain = Constants.SERVER_URL_CN; + + private int QueueCount => eventQueue.Count; + + public EventSender() + { + // 设置计时器 + timer = new Timer(OnTimerElapsed, null, TimeSpan.Zero, TimeSpan.FromSeconds(SendInterval)); + lastSendTime = DateTime.Now; + + // 初始化 HttpClient + var header = new Dictionary + { + { "User-Agent", $"{TapTapSDK.SDKPlatform}/{TapTapSDK.Version}" } + }; + + var coreOptions = TapCoreStandalone.coreOptions; + if (coreOptions.region == TapTapRegionType.CN) + { + domain = Constants.SERVER_URL_CN; + } + else + { + domain = Constants.SERVER_URL_IO; + } + + // 加载未发送的事件 + LoadEvents(); + SendEventsAsync(null); + } + + public async void SendEventsAsync(Action onSendComplete) + { + if (eventQueue.Count == 0) + { + onSendComplete?.Invoke(); + return; + } + + var eventsToSend = new List>(); + for (int i = 0; i < MaxBatchSize && eventQueue.Count > 0; i++) + { + eventsToSend.Add(eventQueue.Dequeue()); + } + + var body = new Dictionary { + { "data", eventsToSend } + }; + + var resonse = await tapHttp.PostJsonAsync(path: $"{domain}/v2/batch", json: body); + if (resonse.IsSuccess) + { + log.Log("Events sent successfully"); + } + else + { + log.Warning("Failed to send events"); + // 将事件重新添加到队列 + foreach (var eventParams in eventsToSend) + { + eventQueue.Enqueue(eventParams); + } + } + onSendComplete?.Invoke(); + SaveEvents(); + } + + public void Send(Dictionary eventParams) + { + // 将事件添加到队列 + eventQueue.Enqueue(eventParams); + SaveEvents(); + + // 检查队列大小 + if (QueueCount >= MaxEvents) + { + SendEvents(); + ResetTimer(); + } + } + + private void OnTimerElapsed(object state) + { + var offset = (DateTime.Now - lastSendTime).TotalSeconds; + if (offset >= SendInterval) + { + SendEvents(); + ResetTimer(); + } + } + + + private void ResetTimer() + { + timer.Change(TimeSpan.FromSeconds(SendInterval), TimeSpan.FromSeconds(SendInterval)); + } + + private void LoadEvents() + { + string filePath = Path.Combine(persistentDataPath, EventFilePath); + if (File.Exists(filePath)) + { + string jsonData = File.ReadAllText(filePath); + if (string.IsNullOrEmpty(jsonData)) + { + return; + } + var savedEvents = ConvertToListOfDictionaries(Json.Deserialize(jsonData)); + if (savedEvents == null) + { + return; + } + foreach (var eventParams in savedEvents) + { + eventQueue.Enqueue(eventParams); + } + } + } + + private void SaveEvents() + { + try + { + if (eventQueue == null) + { + return; + } + + var eventList = eventQueue.ToList(); + string jsonData = Json.Serialize(eventList); + + if (string.IsNullOrEmpty(EventFilePath)) + { + Debug.LogError("EventFilePath is null or empty"); + return; + } + + string filePath = Path.Combine(persistentDataPath, EventFilePath); + + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + File.WriteAllText(filePath, jsonData); + } + catch (Exception ex) + { + Debug.LogError("SaveEvents Exception - " + ex.Message); + } + } + + public void SendEvents() + { + SendEventsAsync(() => lastSendTime = DateTime.Now); + } + + private Dictionary ConvertToDictionary(Dictionary original) + { + var result = new Dictionary(); + foreach (var keyValuePair in original) + { + if (keyValuePair.Value is Dictionary nestedDictionary) + { + result[keyValuePair.Key] = ConvertToDictionary(nestedDictionary); + } + else if (keyValuePair.Value is List nestedList) + { + result[keyValuePair.Key] = ConvertToListOfDictionaries(nestedList); + } + else + { + result[keyValuePair.Key] = keyValuePair.Value; + } + } + return result; + } + private List> ConvertToListOfDictionaries(object deserializedData) + { + if (deserializedData is List list) + { + var result = new List>(); + foreach (var item in list) + { + if (item is Dictionary dictionary) + { + result.Add(ConvertToDictionary(dictionary)); + } + else + { + return null; // 数据格式不匹配 + } + } + return result; + } + return null; // 数据格式不匹配 + } + + [Serializable] + private class Serialization + { + public List items; + public Serialization(List items) + { + this.items = items; + } + + public List ToList() + { + return items; + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/EventSender.cs.meta b/Standalone/Runtime/Internal/EventSender.cs.meta new file mode 100644 index 0000000..7960338 --- /dev/null +++ b/Standalone/Runtime/Internal/EventSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a0c17dc1a104465bb5542984656d6fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http.meta b/Standalone/Runtime/Internal/Http.meta new file mode 100644 index 0000000..4bcbf5d --- /dev/null +++ b/Standalone/Runtime/Internal/Http.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 928b028f42aae4ded9b34d5c4c6536d1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttp.cs b/Standalone/Runtime/Internal/Http/TapHttp.cs new file mode 100644 index 0000000..fcad374 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttp.cs @@ -0,0 +1,377 @@ +using System.Net.Http; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + using System; + using System.Collections.Generic; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using System.Web; + using Newtonsoft.Json; + using TapSDK.Core.Internal.Log; + + public class TapHttp + { + + private static readonly string TAG = "Http"; + + private static readonly int MAX_GET_RETRY_COUNT = 3; + + internal static readonly long CONNECT_TIMEOUT_MILLIS = 10 * 1000L; + internal static readonly long READ_TIMEOUT_MILLIS = 5 * 1000L; + internal static readonly long WRITE_TIMEOUT_MILLIS = 5 * 1000L; + + public static readonly string HOST_CN = "https://tapsdk.tapapis.cn"; + public static readonly string HOST_IO = "https://tapsdk.tapapis.com"; + + private static HttpClient client = new HttpClient(); + + private readonly TapHttpConfig httpConfig; + + private readonly TapLog log = new TapLog(TAG); + + private TapHttp() { } + + internal TapHttp(TapHttpConfig httpConfig) + { + this.httpConfig = httpConfig; + } + + public static TapHttpBuilder NewBuilder(string moduleName, string moduleVersion) + { + return new TapHttpBuilder(moduleName, moduleVersion); + } + + public async void PostJson( + string url, + Dictionary headers = null, + Dictionary queryParams = null, + Dictionary json = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null, + Action onSuccess = null, + Action onFailure = null) + { + TapHttpResult tapHttpResult = await PostJsonAsync( + path: url, + headers: headers, + queryParams: queryParams, + json: json, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy + ); + if (tapHttpResult.IsSuccess) + { + onSuccess?.Invoke(tapHttpResult.Data); + } + else + { + onFailure?.Invoke(tapHttpResult.HttpException); + } + } + + public async void PostForm( + string url, + Dictionary headers = null, + Dictionary queryParams = null, + Dictionary form = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null, + Action onSuccess = null, + Action onFailure = null) + { + TapHttpResult tapHttpResult = await PostFormAsync( + url: url, + headers: headers, + queryParams: queryParams, + form: form, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy + ); + if (tapHttpResult.IsSuccess) + { + onSuccess?.Invoke(tapHttpResult.Data); + } + else + { + onFailure?.Invoke(tapHttpResult.HttpException); + } + } + + public async void Get( + string url, + Dictionary headers = null, + Dictionary queryParams = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null, + Action onSuccess = null, + Action onFailure = null + ) + { + TapHttpResult tapHttpResult = await GetAsync( + url: url, + headers: headers, + queryParams: queryParams, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy + ); + if (tapHttpResult.IsSuccess) + { + onSuccess?.Invoke(tapHttpResult.Data); + } + else + { + onFailure?.Invoke(tapHttpResult.HttpException); + } + } + + public async Task> GetAsync( + string url, + Dictionary headers = null, + Dictionary queryParams = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null + ) + { + if (retryStrategy == null) + { + retryStrategy = TapHttpRetryStrategy.CreateDefault(TapHttpBackoffStrategy.CreateFixed(MAX_GET_RETRY_COUNT)); + } + return await Request( + url: url, + method: HttpMethod.Get, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy, + headers: headers, + queryParams: queryParams + ); + } + + public async Task> PostJsonAsync( + string path, + Dictionary headers = null, + Dictionary queryParams = null, + object json = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null + ) + { + if (retryStrategy == null) + { + retryStrategy = TapHttpRetryStrategy.CreateDefault(TapHttpBackoffStrategy.CreateNone()); + } + string jsonStr = null; + if (json != null) + { + jsonStr = await Task.Run(() => JsonConvert.SerializeObject(json)); + } + return await Request( + url: path, + method: HttpMethod.Post, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy, + headers: headers, + queryParams: queryParams, + body: jsonStr + ); + } + + public async Task> PostFormAsync( + string url, + Dictionary headers = null, + Dictionary queryParams = null, + Dictionary form = null, + bool enableAuthorization = false, + ITapHttpRetryStrategy retryStrategy = null + ) + { + if (retryStrategy == null) + { + retryStrategy = TapHttpRetryStrategy.CreateDefault(TapHttpBackoffStrategy.CreateNone()); + } + return await Request( + url: url, + method: HttpMethod.Post, + enableAuthorization: enableAuthorization, + retryStrategy: retryStrategy, + headers: headers, + queryParams: queryParams, + body: form + ); + } + + private async Task> Request( + string url, + HttpMethod method, + bool enableAuthorization, + ITapHttpRetryStrategy retryStrategy, + Dictionary headers = null, + Dictionary queryParams = null, + object body = null + ) + { + TapHttpResult tapHttpResult; + long nextRetryMillis; + do + { + tapHttpResult = await RequestInner(url, method, enableAuthorization, headers, queryParams, body); + + if (tapHttpResult.IsSuccess) + { + return tapHttpResult; + } + + nextRetryMillis = retryStrategy.NextRetryMillis(tapHttpResult.HttpException); + if (nextRetryMillis > 0) + { + log.Log($"Request failed, retry after {nextRetryMillis} ms"); + await Task.Delay(TimeSpan.FromMilliseconds(nextRetryMillis)); + } + + } while (nextRetryMillis >= 0L); + + return tapHttpResult; + } + + private async Task> RequestInner( + string path, + HttpMethod method, + bool enableAuthorization, + Dictionary headers = null, + Dictionary queryParams = null, + object body = null + ) + { + // 处理查询参数 + Dictionary allQueryParams = new Dictionary(); + if (queryParams != null) + { + foreach (var param in queryParams) + { + allQueryParams[param.Key] = param.Value; + } + } + var fixedQueryParams = httpConfig.Sign.GetFixedQueryParams(); + if (fixedQueryParams != null) + { + foreach (var param in fixedQueryParams) + { + allQueryParams[param.Key] = param.Value; + } + } + string host = HOST_CN; + if (httpConfig.Domain != null) + { + host = httpConfig.Domain; + } + else + { + if (TapCoreStandalone.coreOptions.region == TapTapRegionType.CN) + { + host = HOST_CN; + } + else if (TapCoreStandalone.coreOptions.region == TapTapRegionType.Overseas) + { + host = HOST_IO; + } + } + // 拼接查询参数 + UriBuilder uriBuilder; + if (path.StartsWith("http://") || path.StartsWith("https://")) + { + uriBuilder = new UriBuilder(path); + } + else + { + if (!path.StartsWith("/")) + { + path = "/" + path; + } + uriBuilder = new UriBuilder(uri: $"{host}{path}"); + } + if (allQueryParams.Count > 0) + { + var query = HttpUtility.ParseQueryString(uriBuilder.Query); + foreach (var param in allQueryParams) + { + query[param.Key] = param.Value.ToString(); + } + uriBuilder.Query = query.ToString(); + } + var requestUri = uriBuilder.Uri; + + // 创建 HttpRequestMessage + var request = new HttpRequestMessage + { + Method = method, + RequestUri = requestUri + }; + + // 处理请求头 + Dictionary allHeaders = new Dictionary(); + if (headers != null) + { + foreach (var header in headers) + { + allHeaders[header.Key] = header.Value; + } + } + Dictionary fixedHeaders = httpConfig.Sign.GetFixedHeaders(requestUri.ToString(), method, httpConfig.ModuleName, httpConfig.ModuleVersion, enableAuthorization); + if (fixedHeaders != null) + { + foreach (var header in fixedHeaders) + { + allHeaders[header.Key] = header.Value; + } + } + // 添加请求头 + if (allHeaders != null) + { + foreach (var header in allHeaders) + { + request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString()); + } + } + + // 根据请求类型设置请求体 + if (method == HttpMethod.Post || method == HttpMethod.Put) + { + if (body != null) + { + if (body is string jsonBody) // 处理 JSON 数据 + { + StringContent requestContent = new StringContent(jsonBody); + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + request.Content = requestContent; + } + else if (body is Dictionary formData) // 处理 Form 数据 + { + request.Content = new FormUrlEncodedContent(formData); + } + } + } + // 签算 + httpConfig.Sign.Sign(request); + try + { + if (TapTapSDK.taptapSdkOptions.enableLog) + { + TapHttpUtils.PrintRequest(client, request, request.Content.ReadAsStringAsync().Result); + } + // 发送请求 + HttpResponseMessage response = await client.SendAsync(request); + if (TapTapSDK.taptapSdkOptions.enableLog) + { + TapHttpUtils.PrintResponse(response, response.Content.ReadAsStringAsync().Result); + } + // 解析响应 + return await httpConfig.Parser.Parse(response); + } + catch (Exception ex) + { + // 捕获并处理请求异常 + return TapHttpResult.UnknownFailure(new TapHttpUnknownException(ex)); + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttp.cs.meta b/Standalone/Runtime/Internal/Http/TapHttp.cs.meta new file mode 100644 index 0000000..42a97a4 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2dd246dfad224e9aae739da577b7087 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs b/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs new file mode 100644 index 0000000..9346b46 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs @@ -0,0 +1,92 @@ +using System; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + public class TapHttpBuilder + { + private readonly string moduleName; + private readonly string moduleVersion; + + private string domain = null; + + private ITapHttpSign sign = null; + private ITapHttpParser parser = null; + + private long connectTimeoutMillis = TapHttp.CONNECT_TIMEOUT_MILLIS; + private long readTimeoutMillis = TapHttp.READ_TIMEOUT_MILLIS; + private long writeTimeoutMillis = TapHttp.WRITE_TIMEOUT_MILLIS; + + public TapHttpBuilder(string moduleName, string moduleVersion) + { + this.moduleName = moduleName ?? throw new ArgumentNullException(nameof(moduleName)); + this.moduleVersion = moduleVersion ?? throw new ArgumentNullException(nameof(moduleVersion)); + } + + public TapHttp Build() + { + TapHttpConfig httpConfig = new TapHttpConfig + { + ModuleName = moduleName, + ModuleVersion = moduleVersion, + Domain = domain, + Sign = sign ?? TapHttpSign.CreateDefaultSign(), + Parser = parser ?? TapHttpParser.CreateDefaultParser(), + ConnectTimeoutMillis = connectTimeoutMillis, + ReadTimeoutMillis = readTimeoutMillis, + WriteTimeoutMillis = writeTimeoutMillis, + }; + return new TapHttp(httpConfig); + } + + public TapHttpBuilder ConnectTimeout(long connectTimeoutMillis) + { + this.connectTimeoutMillis = connectTimeoutMillis; + return this; + } + + public TapHttpBuilder ReadTimeout(long readTimeoutMillis) + { + this.readTimeoutMillis = readTimeoutMillis; + return this; + } + + public TapHttpBuilder WriteTimeout(long writeTimeoutMillis) + { + this.writeTimeoutMillis = writeTimeoutMillis; + return this; + } + + public TapHttpBuilder Domain(string domain) + { + this.domain = domain; + return this; + } + + public TapHttpBuilder Sign(ITapHttpSign sign) + { + this.sign = sign; + return this; + } + + public TapHttpBuilder Parser(ITapHttpParser parser) + { + this.parser = parser; + return this; + } + + } + + internal class TapHttpConfig + { + public string ModuleName { get; set; } + public string ModuleVersion { get; set; } + public string Domain { get; set; } + public ITapHttpSign Sign { get; set; } + public ITapHttpParser Parser { get; set; } + public ITapHttpParser RetryStrategy { get; set; } + public long ConnectTimeoutMillis { get; set; } + public long ReadTimeoutMillis { get; set; } + public long WriteTimeoutMillis { get; set; } + } + +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs.meta new file mode 100644 index 0000000..e94cc28 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecc8edfa34fdd462a83db234c7940cec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs b/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs new file mode 100644 index 0000000..ca3b4b9 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs @@ -0,0 +1,114 @@ + +namespace TapSDK.Core.Standalone.Internal.Http +{ + /// + /// HTTP 错误常量类。 + /// + public static class TapHttpErrorConstants + { + + // -------------------------------------------------------------------------------------------- + // 下面的错误信息是服务器端 [data.error] 字段的枚举 + // -------------------------------------------------------------------------------------------- + + /// + /// 400 请阅读:[通用 API 人机验证协议](https://xindong.atlassian.net/wiki/spaces/TAP/pages/66032081)。 + /// + public const string ERROR_CAPTCHA_NEEDS = "captcha.needs"; + + /// + /// 400 人机验证未通过。 + /// + public const string ERROR_CAPTCHA_FAILED = "captcha.failed"; + + /// + /// 403 用户冻结。 + /// + public const string ERROR_USER_IS_DEACTIVATED = "user_is_deactivated"; + + /// + /// 400 请求缺少某个必需参数,包含一个不支持的参数或参数值,或者格式不正确。 + /// + public const string ERROR_INVALID_REQUEST = "invalid_request"; + + /// + /// 404 请求失败,请求所希望得到的资源未被在服务器上发现。在参数相同的情况下,不应该重复请求。 + /// + public const string ERROR_NOT_FOUND = "not_found"; + + /// + /// 403 用户没有对当前动作的权限,引导重新身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。 + /// + public const string ERROR_FORBIDDEN = "forbidden"; + + /// + /// 500 服务器出现异常情况,可稍等后重新尝试请求,但需有尝试上限,建议最多 3 次,如一直失败,则中断并告知用户。 + /// + public const string ERROR_SERVER_ERROR = "server_error"; + + /// + /// 400 客户端时间不正确,应请求服务器时间重新构造。 + /// + public const string ERROR_INVALID_TIME = "invalid_time"; + + /// + /// 400 请求是重放的。 + /// + public const string ERROR_REPLAY_ATTACKS = "replay_attacks"; + + /// + /// 401 client_id、client_secret 参数无效。 + /// + public const string ERROR_INVALID_CLIENT = "invalid_client"; + + /// + /// 400 提供的 Access Grant 是无效的、过期的或已撤销的,例如: Device Code 无效(一个设备授权码只能使用一次)等。 + /// + public const string ERROR_INVALID_GRANT = "invalid_grant"; + + /// + /// 400 服务器不支持 grant_type 参数的值。 + /// + public const string ERROR_UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"; + + /// + /// 400 服务器不支持 response_type 参数的值。 + /// + public const string ERROR_UNSUPPORTED_RESPONSE_TYPE = "unsupported_response_type"; + + /// + /// 400 服务器不支持 secret_type 参数的值。 + /// + public const string ERROR_UNSUPPORTED_SECRET_TYPE = "unsupported_secret_type"; + + /// + /// 400 Device Flow 中,设备通过 Device Code 换取 Access Token 的接口过于频繁。 + /// + public const string ERROR_SLOW_DOWN = "slow_down"; + + /// + /// 429 登录尝试次数过多,请稍后重试,用于 password 模式下出错次数过多。 + /// + public const string ERROR_TOO_MANY_LOGIN_ATTEMPTS = "too_many_login_attempts"; + + /// + /// 401 授权服务器拒绝请求,这个状态出现在拿着 token 请求用户资源时,如出现,客户端应退出本地的用户登录信息,引导用户重新登录。 + /// + public const string ERROR_ACCESS_DENIED = "access_denied"; + + /// + /// 401 认证内容无效 grant_type 为 password 的模式下,用户名或密码错误。 + /// + public const string ERROR_INVALID_CREDENTIALS = "invalid_credentials"; + + /// + /// 400 Device Flow 中,用户还没有对 Device Code 完成授权操作,按 interval 要求频率继续轮询,直到 expires_in 过期。 + /// + public const string ERROR_AUTHORIZATION_PENDING = "authorization_pending"; + + /// + /// 服务端业务异常,如:防沉迷 token 失效(code=20000)。 + /// + public const string ERROR_BUSINESS_ERROR = "business_code_error"; + } +} diff --git a/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs.meta new file mode 100644 index 0000000..38a2635 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpErrorConstants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1ac950db03b74f6a8e85a3023f72fd6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpException.cs b/Standalone/Runtime/Internal/Http/TapHttpException.cs new file mode 100644 index 0000000..4dfb519 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpException.cs @@ -0,0 +1,71 @@ +using System; +using System.Net; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + public abstract class AbsTapHttpException : Exception + { + public AbsTapHttpException(string message) : base(message) + { + } + + protected AbsTapHttpException(string message, Exception e) : base(message, e) + { + } + } + + public class TapHttpUnknownException : AbsTapHttpException + { + public TapHttpUnknownException(Exception e) : base("Unknown error", e) + { + } + } + + public class TapHttpInvalidResponseException : AbsTapHttpException + { + public HttpStatusCode StatusCode { get; } + + public TapHttpInvalidResponseException(HttpStatusCode statusCode, string message) + : base(message) + { + StatusCode = statusCode; + } + } + + /// + /// 表示 TapSDK 中与 Http 相关的服务端返回的错误信息。 + /// + public class TapHttpServerException : AbsTapHttpException + { + /// + /// 获取服务器返回的 HTTP 状态码。 + /// + public HttpStatusCode StatusCode { get; } + + /// + /// 获取服务器返回的 Response。 + /// + public TapHttpResponse TapHttpResponse { get; } + + /// + /// 获取服务器返回的 Response Error Data。 + /// + public TapHttpErrorData ErrorData { get; } + + /// + /// 初始化 类的新实例。 + /// + /// 服务器返回的 HTTP 状态码。 + /// 与异常相关的自定义错误代码。 + /// 错误消息。 + /// 服务器返回的错误标识符。 + /// 错误的详细描述。 + public TapHttpServerException(HttpStatusCode statusCode, TapHttpResponse tapHttpResponse, TapHttpErrorData tapHttpErrorData) + : base(tapHttpErrorData.Msg) + { + StatusCode = statusCode; + TapHttpResponse = tapHttpResponse ?? throw new ArgumentNullException(nameof(tapHttpResponse)); + ErrorData = tapHttpErrorData ?? throw new ArgumentNullException(nameof(tapHttpErrorData)); + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttpException.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpException.cs.meta new file mode 100644 index 0000000..c8e8bdc --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3f8dc471e1b04052ba0833a4dec7cee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpParser.cs b/Standalone/Runtime/Internal/Http/TapHttpParser.cs new file mode 100644 index 0000000..1cd5f17 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpParser.cs @@ -0,0 +1,132 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + /// + /// 表示一个 HTTP 解析器的接口。 + /// Represents an HTTP parser interface. + /// + public interface ITapHttpParser + { + /// + /// 解析 HTTP 响应。 + /// Parses the HTTP response. + /// + /// 解析后返回的对象类型。The type of the object to return after parsing. + /// HTTP 响应消息。The HTTP response message. + /// 解析结果。The parsing result. + Task> Parse(HttpResponseMessage response); + } + + /// + /// 提供 HTTP 解析功能的类。 + /// Class that provides HTTP parsing functionality. + /// + public class TapHttpParser + { + /// + /// 创建默认的 HTTP 解析器。 + /// Creates a default HTTP parser. + /// + /// 返回一个实现了 ITapHttpParser 接口的解析器。Returns a parser that implements the ITapHttpParser interface. + public static ITapHttpParser CreateDefaultParser() + { + return new Default(); + } + + public static ITapHttpParser CreateEventParser() + { + return new Event(); + } + + private class Event : ITapHttpParser + { + public Task> Parse(HttpResponseMessage response) + { + _ = response ?? throw new ArgumentNullException(nameof(response)); + HttpStatusCode statusCode = response.StatusCode; + if (statusCode >= HttpStatusCode.OK && statusCode < HttpStatusCode.MultipleChoices) + { + return Task.FromResult(TapHttpResult.Success(default)); + } + else + { + return Task.FromResult(TapHttpResult.UnknownFailure(default)); + } + } + + } + + private class Default : ITapHttpParser + { + /// + /// 解析 HTTP 响应。 + /// Parses the HTTP response. + /// + /// 解析后返回的对象类型。The type of the object to return after parsing. + /// HTTP 响应消息。The HTTP response message. + /// 解析结果。The parsing result. + public async Task> Parse(HttpResponseMessage response) + { + _ = response ?? throw new ArgumentNullException(nameof(response)); + HttpStatusCode statusCode = response.StatusCode; + // 读取响应内容 + // Read the response content + string content = await response.Content.ReadAsStringAsync(); + + // 处理响应 + // Handle the response + TapHttpResponse httpResponse; + try + { + httpResponse = JsonConvert.DeserializeObject(content); + } + catch (Exception) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "Failed to parse TapHttpResponse")); + } + if (httpResponse == null) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "TapHttpResponse is null")); + } + + if (httpResponse.Success) + { + // 修正时间 + // Fix the time + TapHttpTime.FixTime(httpResponse.Now); + if (httpResponse.Data == null) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "TapHttpResponse.Data is null")); + } + try + { + T data = httpResponse.Data.ToObject(); + if (data == null) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "TapHttpResponse.Data is null")); + } + return TapHttpResult.Success(data); + } + catch (Exception) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "Failed to parse TapHttpResponse.Data")); + } + } + else + { + TapHttpErrorData httpErrorData = httpResponse.Data.ToObject(); + if (httpErrorData == null) + { + return TapHttpResult.InvalidResponseFailure(new TapHttpInvalidResponseException(statusCode, "TapHttpErrorData is null")); + } + return TapHttpResult.ServerFailure(new TapHttpServerException((HttpStatusCode)500, httpResponse, httpErrorData)); + } + } + } + } +} diff --git a/Standalone/Runtime/Internal/Http/TapHttpParser.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpParser.cs.meta new file mode 100644 index 0000000..cd6cb31 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab2c195f6902f4a6da00ffc255b8378d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpResponse.cs b/Standalone/Runtime/Internal/Http/TapHttpResponse.cs new file mode 100644 index 0000000..c4176a4 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpResponse.cs @@ -0,0 +1,37 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + + [Serializable] + public class TapHttpResponse + { + [JsonProperty("data")] + public JObject Data { get; private set; } + + [JsonProperty("success")] + public bool Success { get; private set; } + + [JsonProperty("now")] + public int Now { get; private set; } + } + + [Serializable] + public class TapHttpErrorData + { + [JsonProperty("code")] + public int Code { get; private set; } + + [JsonProperty("msg")] + public string Msg { get; private set; } + + [JsonProperty("error")] + public string Error { get; private set; } + + [JsonProperty("error_description")] + public string ErrorDescription { get; private set; } + } + +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttpResponse.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpResponse.cs.meta new file mode 100644 index 0000000..de1601c --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c88fae64652a4577a0f71aafa0f468a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpResult.cs b/Standalone/Runtime/Internal/Http/TapHttpResult.cs new file mode 100644 index 0000000..dab58b5 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpResult.cs @@ -0,0 +1,90 @@ +using System; +using System.Net; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + /// + /// 表示 HTTP 错误类型的枚举。 + /// + + /// + /// 表示 TapSDK 中 HTTP 请求的结果。 + /// + public class TapHttpResult + { + /// + /// 指示请求是否成功。 + /// + public bool IsSuccess { get; private set; } + + /// + /// HTTP 请求的响应内容。 + /// + public T Data { get; private set; } + + /// + /// 错误类型,区分网络错误、客户端错误、服务器错误等。 + /// + public AbsTapHttpException HttpException { get; private set; } + + /// + /// 私有构造函数,防止直接实例化。 + /// + private TapHttpResult() + { + } + + /// + /// 创建一个成功的 HTTP 请求结果。 + /// + /// HTTP 响应的内容。 + /// TapHttpResult 对象,表示成功的请求。 + public static TapHttpResult Success(T data) + { + return new TapHttpResult + { + IsSuccess = true, + Data = data, + }; + } + + /// + /// 创建一个失败的 HTTP 请求结果,通常是网络错误或客户端错误。 + /// + /// 错误类型,例如网络错误或客户端错误。 + /// 异常对象,用于传递错误详情(可选)。 + /// TapHttpResult 对象,表示失败的请求。 + public static TapHttpResult InvalidResponseFailure(TapHttpInvalidResponseException exception) + { + return new TapHttpResult + { + IsSuccess = false, + HttpException = exception + }; + } + + /// + /// 创建一个服务端返回的错误结果。 + /// + /// 包含详细服务端错误信息的异常对象。 + /// TapHttpResult 对象,表示服务端错误的请求。 + public static TapHttpResult ServerFailure(TapHttpServerException httpException) + { + return new TapHttpResult + { + IsSuccess = false, + HttpException = httpException, + }; + } + + public static TapHttpResult UnknownFailure(TapHttpUnknownException httpException) + { + return new TapHttpResult + { + IsSuccess = false, + HttpException = httpException, + }; + } + + } +} diff --git a/Standalone/Runtime/Internal/Http/TapHttpResult.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpResult.cs.meta new file mode 100644 index 0000000..bcfeb8f --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c7f303ab4c084015a8c2b75cb268c04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs b/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs new file mode 100644 index 0000000..6110113 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs @@ -0,0 +1,237 @@ +using System; +using System.Net; +using System.Threading; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + /// + /// 重试策略接口。 + /// + public interface ITapHttpRetryStrategy + { + /// + /// 获取下次重试的时间(毫秒)。 + /// + /// 错误类型。 + /// 异常信息。 + /// 下次重试的时间(毫秒),如果不重试返回 -1。 + long NextRetryMillis(AbsTapHttpException e); + } + + /// + /// 后退策略接口。 + /// + public interface ITapHttpBackoffStrategy + { + /// + /// 获取下一个后退的时间(毫秒)。 + /// + /// 下一个后退的时间(毫秒)。 + long NextBackoffMillis(); + + /// + /// 判断是否可以重试无效时间。 + /// + /// 如果可以重试返回 true,否则返回 false。 + bool CanInvalidTimeRetry(); + + /// + /// 重置策略状态。 + /// + void Reset(); + } + + /// + /// HTTP 重试策略实现。 + /// + public class TapHttpRetryStrategy + { + /// + /// 创建默认重试策略。 + /// + /// 后退策略。 + /// 默认重试策略。 + public static ITapHttpRetryStrategy CreateDefault(ITapHttpBackoffStrategy backoffStrategy) + { + return new Default(backoffStrategy); + } + + /// + /// 创建不重试策略。 + /// + /// 不重试策略。 + public static ITapHttpRetryStrategy CreateNone() + { + return new None(); + } + + private class None : ITapHttpRetryStrategy + { + public long NextRetryMillis(AbsTapHttpException e) + { + // 不重试返回 -1 + return -1L; + } + } + + private class Default : ITapHttpRetryStrategy + { + private readonly ITapHttpBackoffStrategy backoffStrategy; + + public Default(ITapHttpBackoffStrategy backoffStrategy) + { + this.backoffStrategy = backoffStrategy; + } + + public long NextRetryMillis(AbsTapHttpException e) + { + long nextRetryMillis = -1L; + if (e is TapHttpServerException se) + { + // 处理服务器错误状态码 + if (se.StatusCode >= HttpStatusCode.InternalServerError && se.StatusCode <= (HttpStatusCode)599) + { + nextRetryMillis = backoffStrategy.NextBackoffMillis(); + } + else if (TapHttpErrorConstants.ERROR_INVALID_TIME.Equals(se.ErrorData.Error)) + { + // 修复时间并判断是否可以重试 + TapHttpTime.FixTime(se.TapHttpResponse.Now); + if (backoffStrategy.CanInvalidTimeRetry()) + { + nextRetryMillis = 0L; // 立马重试 + } + } + else if (TapHttpErrorConstants.ERROR_SERVER_ERROR.Equals(se.ErrorData.Error)) + { + nextRetryMillis = backoffStrategy.NextBackoffMillis(); + } + } + else if (e is TapHttpInvalidResponseException ie) + { + if (ie.StatusCode >= HttpStatusCode.InternalServerError && ie.StatusCode <= (HttpStatusCode)599) + { + nextRetryMillis = backoffStrategy.NextBackoffMillis(); + } + } + return nextRetryMillis; + } + } + } + + /// + /// HTTP 后退策略实现。 + /// + public class TapHttpBackoffStrategy + { + /// + /// 创建固定后退策略。 + /// + /// 最大重试次数。 + /// 固定后退策略。 + public static ITapHttpBackoffStrategy CreateFixed(int maxCount) + { + return new Fixed(maxCount); + } + + /// + /// 创建指数后退策略。 + /// + /// 指数后退策略。 + public static ITapHttpBackoffStrategy CreateExponential() + { + return new Exponential(); + } + + /// + /// 创建不后退策略。 + /// + /// 不后退策略。 + public static ITapHttpBackoffStrategy CreateNone() + { + return new None(); + } + + private abstract class Base : ITapHttpBackoffStrategy + { + protected int CanTimeDeltaRetry = 1; + + public abstract long NextBackoffMillis(); + + public abstract void Reset(); + + /// + /// 判断是否可以重试无效时间。 + /// + /// 如果可以重试返回 true,否则返回 false。 + public bool CanInvalidTimeRetry() + { + return Interlocked.CompareExchange(ref CanTimeDeltaRetry, 0, 1) == 1; + } + } + + private class Fixed : Base + { + private readonly int _maxCount; + private int CurrentCount = 0; + + public Fixed(int maxCount) + { + _maxCount = maxCount; + } + + public override long NextBackoffMillis() + { + if (++CurrentCount < _maxCount) + { + return 100L; // 固定的重试时间 100ms + } + return -1L; // 达到最大重试次数,返回 -1 + } + + public override void Reset() + { + CurrentCount = 0; + Interlocked.Exchange(ref CanTimeDeltaRetry, 1); + } + } + + private class Exponential : Base + { + private static readonly long INIT_INTERVAL_MILLIS = 2 * 1000L; // 初始时间 2 秒 + private static readonly long MAX_INTERVAL_MILLIS = 600 * 1000L; // 最大时间 600 秒 + private static readonly int MULTIPLIER = 2; // 指数倍数 + + private long CurrentIntervalMillis = INIT_INTERVAL_MILLIS; + + public override long NextBackoffMillis() + { + if (CurrentIntervalMillis * MULTIPLIER > MAX_INTERVAL_MILLIS) + { + return MAX_INTERVAL_MILLIS; // 返回最大时间 + } + CurrentIntervalMillis *= MULTIPLIER; // 增加当前时间 + return CurrentIntervalMillis; + } + + public override void Reset() + { + CurrentIntervalMillis = INIT_INTERVAL_MILLIS / MULTIPLIER; // 重置当前时间 + Interlocked.Exchange(ref CanTimeDeltaRetry, 1); + } + } + + private class None : Base + { + public override long NextBackoffMillis() + { + return -1L; // 不后退,返回 -1 + } + + public override void Reset() + { + Interlocked.Exchange(ref CanTimeDeltaRetry, 1); + } + } + } +} diff --git a/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs.meta new file mode 100644 index 0000000..813de2f --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpRetryStrategy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88883d9f22f9042c19eb909edcc7f851 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpSign.cs b/Standalone/Runtime/Internal/Http/TapHttpSign.cs new file mode 100644 index 0000000..3a6c5e8 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpSign.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + + /// + /// 定义 HTTP 签名相关操作的接口。 + /// + public interface ITapHttpSign + { + /// + /// 获取固定的 HTTP 请求头信息。 + /// + /// 返回包含固定请求头的字典。 + Dictionary GetFixedHeaders(string url, HttpMethod method, string moduleName, string moduleVersion, bool enableAuthorization); + + /// + /// 获取固定的查询参数。 + /// + /// 返回包含固定查询参数的字典。 + Dictionary GetFixedQueryParams(); + + /// + /// 对 HTTP 请求数据进行签名处理。 + /// + /// 包含请求数据的 对象。 + void Sign(HttpRequestMessage signData); + } + + public class TapHttpSign + { + public static ITapHttpSign CreateDefaultSign() + { + return new Default(); + } + + public static ITapHttpSign CreateNoneSign() + { + return new None(); + } + + private class Default : ITapHttpSign + { + public Dictionary GetFixedHeaders(string url, HttpMethod method, string moduleName, string moduleVersion, bool enableAuthorization) + { + _ = method ?? throw new ArgumentNullException(nameof(method)); + _ = moduleName ?? throw new ArgumentNullException(nameof(moduleName)); + _ = moduleVersion ?? throw new ArgumentNullException(nameof(moduleVersion)); + + if (method == HttpMethod.Post || method == HttpMethod.Get) + { + Dictionary headers = new Dictionary + { + { "X-Tap-PN", "TapSDK" }, + { "X-Tap-Device-Id", TapHttpUtils.GenerateDeviceId()}, + { "X-Tap-Platform", "PC"}, + { "X-Tap-SDK-Module", moduleName}, + { "X-Tap-SDK-Module-Version", moduleVersion}, + { "X-Tap-SDK-Artifact", "Unity"}, + { "X-Tap-Ts", TapHttpUtils.GenerateTime()}, + { "X-Tap-Nonce", TapHttpUtils.GenerateNonce()}, + { "X-Tap-Lang", TapHttpUtils.GenerateLanguage()}, + { "User-Agent", TapHttpUtils.GenerateUserAgent()}, + }; + if (enableAuthorization) + { + string authorization = TapHttpUtils.GenerateAuthorization(url, method.ToString()); + if (authorization != null) + { + headers.Add("Authorization", authorization); + } + } + return headers; + } + return null; + } + + public Dictionary GetFixedQueryParams() + { + return new Dictionary + { + { "client_id", TapCoreStandalone.coreOptions.clientId } + }; + } + + public async void Sign(HttpRequestMessage requestMessage) + { + _ = requestMessage ?? throw new ArgumentNullException(nameof(requestMessage)); + + string clientToken = TapCoreStandalone.coreOptions.clientToken; + string methodPart = requestMessage.Method.Method; + string urlPathAndQueryPart = requestMessage.RequestUri.PathAndQuery; + var headerKeys = requestMessage.Headers + .Where(h => h.Key.StartsWith("x-tap-", StringComparison.OrdinalIgnoreCase)) + .OrderBy(h => h.Key.ToLowerInvariant()) + .Select(h => $"{h.Key.ToLowerInvariant()}:{string.Join(",", h.Value)}") + .ToList(); + string headersPart = string.Join("\n", headerKeys); + string bodyPart = string.Empty; + if (requestMessage.Content != null) + { + bodyPart = await requestMessage.Content.ReadAsStringAsync(); + } + string signParts = methodPart + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + bodyPart + "\n"; + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(clientToken))) + { + byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signParts)); + string sign = Convert.ToBase64String(hash); + requestMessage.Headers.Add("X-Tap-Sign", sign); + } + } + } + + private class None : ITapHttpSign + { + public Dictionary GetFixedHeaders(string url, HttpMethod method, string moduleName, string moduleVersion, bool enableAuthorization) + { + return null; + } + + public Dictionary GetFixedQueryParams() + { + return null; + } + + public void Sign(HttpRequestMessage signData) + { + // do nothing + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttpSign.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpSign.cs.meta new file mode 100644 index 0000000..6861850 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpSign.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a87a746ca4bed45e09d0997c60660752 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Http/TapHttpUtils.cs b/Standalone/Runtime/Internal/Http/TapHttpUtils.cs new file mode 100644 index 0000000..9f9cd66 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpUtils.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using TapSDK.Core.Internal.Log; +using TapSDK.Core.Standalone.Internal.Service; +using UnityEngine; + +namespace TapSDK.Core.Standalone.Internal.Http +{ + public static class TapHttpTime + { + private static int timeOffset = 0; + private static void SetTimeOffset(int offset) + { + timeOffset = offset; + } + + // 获取当前时间的秒级时间戳 + public static int GetCurrentTime() + { + DateTime epochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + TimeSpan timeSpan = DateTime.UtcNow - epochStart; + return (int)timeSpan.TotalSeconds + timeOffset; + } + + public static void FixTime(int time) + { + if (time == 0) + { + return; + } + SetTimeOffset(time - (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds); + } + } + + public static class TapHttpUtils + { + + private static readonly TapLog tapLog = new TapLog("Http"); + + internal static string GenerateNonce() + { + string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + char[] nonce = new char[10]; + for (int i = 0; i < 10; i++) + { + nonce[i] = chars[UnityEngine.Random.Range(0, chars.Length)]; + } + return new string(nonce); + } + + internal static string GenerateUserAgent() + { + return $"TapSDK-Unity/{TapTapSDK.Version}"; + } + + internal static string GenerateTime() + { + return TapHttpTime.GetCurrentTime().ToString(); + } + + internal static string GenerateLanguage() + { + return Tracker.getServerLanguage(); + } + + internal static string GenerateDeviceId() + { + return SystemInfo.deviceUniqueIdentifier; + } + + internal static string GenerateAuthorization(string url, string method) + { + Type interfaceType = typeof(ITapLoginService); + Type[] initTaskTypes = AppDomain.CurrentDomain.GetAssemblies() + .Where(asssembly => + { + string fullName = asssembly.GetName().FullName; + return fullName.StartsWith("TapSDK.Login.Standalone.Runtime"); + }) + .SelectMany(assembly => assembly.GetTypes()) + .Where(clazz => + { + return interfaceType.IsAssignableFrom(clazz) && clazz.IsClass; + }) + .ToArray(); + if (initTaskTypes.Length != 1) + { + return null; + } + try + { + ITapLoginService tapLoginService = Activator.CreateInstance(initTaskTypes[0]) as ITapLoginService; + string authorization = tapLoginService.ObtainAuthorizationAsync(url, method); + return authorization; + } + catch (Exception e) + { + TapLog.Error("e = " + e); + } + return null; + } + + public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) + { + if (TapLogger.LogDelegate == null) + { + return; + } + if (client == null) + { + return; + } + if (request == null) + { + return; + } + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=== HTTP Request Start ==="); + sb.AppendLine($"URL: {request.RequestUri}"); + sb.AppendLine($"Method: {request.Method}"); + sb.AppendLine($"Headers: "); + foreach (var header in client.DefaultRequestHeaders) + { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + foreach (var header in request.Headers) + { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + if (request.Content != null) + { + foreach (var header in request.Content.Headers) + { + sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + } + } + if (!string.IsNullOrEmpty(content)) + { + sb.AppendLine($"Content: \n{content}"); + } + sb.AppendLine("=== HTTP Request End ==="); + tapLog.Log($"HTTP Request [{request.RequestUri.PathAndQuery}]", sb.ToString()); + } + + public static void PrintResponse(HttpResponseMessage response, string content = null) + { + if (TapLogger.LogDelegate == null) + { + return; + } + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=== HTTP Response Start ==="); + sb.AppendLine($"URL: {response.RequestMessage.RequestUri}"); + sb.AppendLine($"Status Code: {response.StatusCode}"); + if (!string.IsNullOrEmpty(content)) + { + sb.AppendLine($"Content: {content}"); + } + sb.AppendLine("=== HTTP Response End ==="); + tapLog.Log($"HTTP Response [{response.RequestMessage.RequestUri.PathAndQuery}]", sb.ToString()); + } + + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Http/TapHttpUtils.cs.meta b/Standalone/Runtime/Internal/Http/TapHttpUtils.cs.meta new file mode 100644 index 0000000..70e8a89 --- /dev/null +++ b/Standalone/Runtime/Internal/Http/TapHttpUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fcfd5c4fd9c24270b68ffa8cd4f93a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Identity.cs b/Standalone/Runtime/Internal/Identity.cs new file mode 100644 index 0000000..841279c --- /dev/null +++ b/Standalone/Runtime/Internal/Identity.cs @@ -0,0 +1,43 @@ +using System; +using UnityEngine; + +namespace TapSDK.Core.Standalone.Internal { + public class Identity { + public static readonly string DEVICE_ID_KEY = "tapdb_unique_id"; + public static readonly string PERSISTENT_ID_KEY = "tapdb_persist_id"; + public static readonly string INSTALLATION_ID_KEY = "tapdb_install_id"; + + public static string DeviceId { + get { + string deviceId = TapCoreStandalone.Prefs.Get(DEVICE_ID_KEY); + if (string.IsNullOrWhiteSpace(deviceId)) { + deviceId = SystemInfo.deviceUniqueIdentifier; + TapCoreStandalone.Prefs.Set(DEVICE_ID_KEY, deviceId); + } + return deviceId; + } + } + + public static string PersistentId { + get { + string persistentId = TapCoreStandalone.Prefs.Get(PERSISTENT_ID_KEY); + if (string.IsNullOrWhiteSpace(persistentId)) { + persistentId = Guid.NewGuid().ToString(); + TapCoreStandalone.Prefs.Set(PERSISTENT_ID_KEY, persistentId); + } + return persistentId; + } + } + + public static string InstallationId { + get { + string installationId = TapCoreStandalone.Prefs.Get(INSTALLATION_ID_KEY); + if (string.IsNullOrWhiteSpace(installationId)) { + installationId = Guid.NewGuid().ToString(); + TapCoreStandalone.Prefs.Set(INSTALLATION_ID_KEY, installationId); + } + return installationId; + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Identity.cs.meta b/Standalone/Runtime/Internal/Identity.cs.meta new file mode 100644 index 0000000..a2bb270 --- /dev/null +++ b/Standalone/Runtime/Internal/Identity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2dafe2e45386e48e797494ee95c9a4d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog.meta b/Standalone/Runtime/Internal/Openlog.meta new file mode 100644 index 0000000..2f5aa92 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93aaba645bba840eb88fe51d138c10fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/Bean.meta b/Standalone/Runtime/Internal/Openlog/Bean.meta new file mode 100644 index 0000000..a986dc7 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/Bean.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e49d7f2e1e5b4eaf90b7cf14770d8f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs new file mode 100644 index 0000000..9c9a89a --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs @@ -0,0 +1,39 @@ +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN +using Newtonsoft.Json; +using ProtoBuf; +using System.Collections.Generic; + +namespace TapSDK.Core.Standalone.Internal +{ + [ProtoContract] + public class LogContent + { + [JsonProperty("Key")] + [ProtoMember(1)] + public string Key { get; set; } + + [JsonProperty("Value")] + [ProtoMember(2)] + public string Value { get; set; } + } + + [ProtoContract] + public class Log + { + [JsonProperty("Value")] + [ProtoMember(1)] + public uint Time { get; set; } + [JsonProperty("Contents")] + [ProtoMember(2)] + public List Contents { get; set; } + } + + [ProtoContract] + public class LogGroup + { + [JsonProperty("Logs")] + [ProtoMember(1)] + public List Logs { get; set; } + } +} +#endif diff --git a/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs.meta b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs.meta new file mode 100644 index 0000000..585ab76 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogLogGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3408a0ca858fb4782bacbdbca16a5a41 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs new file mode 100644 index 0000000..da198d4 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace TapSDK.Core.Standalone.Internal.Openlog +{ + public class TapOpenlogStoreBean + { + [JsonProperty("action")] + public string action; + + [JsonProperty("timestamp")] + public long timestamp; + + [JsonProperty("t_log_id")] + public string tLogId; + + [JsonProperty("props")] + public readonly Dictionary props; + + public TapOpenlogStoreBean(string action, long timestamp, string tLogId, Dictionary props) + { + this.action = action; + this.timestamp = timestamp; + this.tLogId = tLogId; + this.props = props; + } + + public override string ToString() + { + return $"TapOpenlogStoreBean(action: {action} , tLogId: {tLogId} , timestamp: {timestamp})"; + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs.meta b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs.meta new file mode 100644 index 0000000..b21baa6 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/Bean/TapOpenlogStoreBean.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8dc8accebab264d03a71480ce67b1bf3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs new file mode 100644 index 0000000..743c60e --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs @@ -0,0 +1,153 @@ +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN +using System; +using System.Threading.Tasks; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using TapSDK.Core.Internal.Log; +using K4os.Compression.LZ4; + +namespace TapSDK.Core.Standalone.Internal +{ + public class TapOpenlogHttpClient + { + + private static readonly string HOST_CN = "openlog.tapapis.cn"; + private static readonly string HOST_IO = "openlog.tapapis.com"; + + private static string GetHost() + { + if (TapCoreStandalone.coreOptions.region == TapTapRegionType.CN) + { + return HOST_CN; + } + else if (TapCoreStandalone.coreOptions.region == TapTapRegionType.Overseas) + { + return HOST_IO; + } + else + { + return HOST_CN; + } + } + + private static TapLog log = new TapLog(module: "Openlog.HttpClient"); + + private static HttpClient client; + + public TapOpenlogHttpClient() + { + client = new HttpClient(); + } + + public async Task Post(string path, byte[] content) + { + string url = BuildUrl(path); + var request = new HttpRequestMessage(HttpMethod.Post, url); + + request.Headers.Add("User-Agent", "TapSDK-Unity/" + TapTapSDK.Version); + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("x-log-apiversion", "0.6.0"); + request.Headers.Add("x-log-compresstype", "lz4"); + request.Headers.Add("x-log-signaturemethod", "hmac-sha1"); + request.Headers.Add("x-log-timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()); + request.Headers.Add("x-log-bodyrawsize", content.Length.ToString()); + + byte[] compressContent = LZ4Compress(content); + + var contentMD5 = EncryptString(compressContent); + request.Headers.Add("x-content-md5", contentMD5); + + string methodPart = request.Method.Method; + string urlPath = request.RequestUri.LocalPath; + string headersPart = GetHeadersPart(request.Headers); + string signParts = methodPart + "\n" + contentMD5 + "\n" + "application/x-protobuf" + "\n" + headersPart + "\n" + urlPath; + string sign; + using (var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(TapCoreStandalone.coreOptions.clientToken))) + { + byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signParts)); + sign = Convert.ToBase64String(hash); + } + + request.Headers.Add("Authorization", "LOG " + TapCoreStandalone.coreOptions.clientId + ":" + sign); + + ByteArrayContent requestContent = new ByteArrayContent(compressContent); + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-protobuf"); + request.Content = requestContent; + + return await SendRequest(request); + } + + private static string EncryptString(byte[] str) + { + var md5 = MD5.Create(); + byte[] byteOld = str; + byte[] byteNew = md5.ComputeHash(byteOld); + var sb = new StringBuilder(); + foreach (byte b in byteNew) + { + sb.Append(b.ToString("X2")); + } + return sb.ToString(); + } + + private static byte[] LZ4Compress(byte[] data) + { + int maxCompressedLength = LZ4Codec.MaximumOutputSize(data.Length); + byte[] compressedData = new byte[maxCompressedLength]; + int compressedLength = LZ4Codec.Encode(data, 0, data.Length, compressedData, 0, compressedData.Length); + + byte[] result = new byte[compressedLength]; + Array.Copy(compressedData, result, compressedLength); + return result; + } + + private static string GetHeadersPart(HttpRequestHeaders headers) + { + var headerKeys = headers + .Where(h => h.Key.StartsWith("x-log-", StringComparison.OrdinalIgnoreCase)) + .OrderBy(h => h.Key.ToLowerInvariant()) + .Select(h => $"{h.Key.ToLowerInvariant()}:{string.Join(",", h.Value)}") + .ToList(); + + return string.Join("\n", headerKeys); + } + + private async Task SendRequest(HttpRequestMessage request) + { + HttpResponseMessage response; + try + { + response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + request.Dispose(); + } + catch (HttpRequestException e) + { + log.Warning($"Request error", e.ToString()); + return false; + } + + if (response.IsSuccessStatusCode || (response.StatusCode >= HttpStatusCode.BadRequest && response.StatusCode < HttpStatusCode.InternalServerError)) + { + response.Dispose(); + return true; + } + else + { + log.Warning($"SendOpenlog failed", response.StatusCode.ToString()); + response.Dispose(); + return false; + } + } + + private static string BuildUrl(string path) + { + string url = $"https://{GetHost()}/{path}?client_id={TapCoreStandalone.coreOptions.clientId}"; + return url; + } + } +} +#endif \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs.meta new file mode 100644 index 0000000..42b1400 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogHttpClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db5b5756edc624e2fbb3bc1f01459814 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs new file mode 100644 index 0000000..632a855 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs @@ -0,0 +1,104 @@ +namespace TapSDK.Core.Standalone.Internal.Openlog +{ + internal class TapOpenlogParamConstants + { + // 该条日志的唯一标识 d + public const string PARAM_T_LOG_ID = "t_log_id"; + + // 客户端时区,eg:Asia/Shanghai d + public const string PARAM_TIMEZONE = "timezone"; + + // 客户端生成的时间戳,毫秒级 d + public const string PARAM_TIMESTAMP = "timestamp"; + + // 应用包名 g + public const string PARAM_APP_PACKAGE_NAME = "app_package_name"; + + // 应用版本字符串 g + public const string PARAM_APP_VERSION = "app_version"; + + // 应用版本(数字) g + public const string PARAM_APP_VERSION_CODE = "app_version_code"; + + // 固定一个枚举值: TapSDK g + public const string PARAM_PN = "pn"; + + // SDK接入项目具体模块枚举值 d + public const string PARAM_TAPSDK_PROJECT = "tapsdk_project"; + + // SDK 模块版本号 d + public const string PARAM_TAPSDK_VERSION = "tapsdk_version"; + + // SDK 产物类型 d + public const string PARAM_TAPSDK_ARTIFACT = "tapsdk_artifact"; + + // SDK 运行平台 g + public const string PARAM_PLATFORM = "platform"; + + // 埋点版本号(预留字段),当前全为1 g + public const string PARAM_TRACK_CODE = "track_code"; + + // SDK设置的地区,例如 zh_CN d + public const string PARAM_SDK_LOCALE = "sdk_locale"; + + // 游戏账号 ID(非角色 ID)d + public const string PARAM_GAME_USER_ID = "game_user_id"; + + // SDK生成的设备全局唯一标识 d + public const string PARAM_GID = "gid"; + + // SDK生成的设备唯一标识 d + public const string PARAM_DEVICE_ID = "device_id"; + + // SDK生成的设备一次安装的唯一标识 d + public const string PARAM_INSTALL_UUID = "install_uuid"; + + // 设备品牌,eg: Xiaomi d + public const string PARAM_DV = "dv"; + + // 设备品牌型号,eg:21051182C d + public const string PARAM_MD = "md"; + + // 设备CPU型号,eg:arm64-v8a d + public const string PARAM_CPU = "cpu"; + + // 支持 CPU 架构,eg:arm64-v8a d + public const string PARAM_CPU_ABIS = "cpu_abis"; + + // 设备操作系统 d + public const string PARAM_OS = "os"; + + // 设备操作系统版本 d + public const string PARAM_SV = "sv"; + + // 物理设备真实屏幕分辨率宽 g + public const string PARAM_WIDTH = "width"; + + // 物理设备真实屏幕分辨率高 g + public const string PARAM_HEIGHT = "height"; + + // 设备可用存储空间(磁盘),单位B d + public const string PARAM_ROM = "rom"; + + // 设备可用内存,单位B d + public const string PARAM_RAM = "ram"; + + // 设备总存储空间(磁盘),单位B g + public const string PARAM_TOTAL_ROM = "total_rom"; + + // 设备总内存,单位B g + public const string PARAM_TOTAL_RAM = "total_ram"; + + // 芯片型号,eg:Qualcomm Technologies, Inc SM7250 g + public const string PARAM_HARDWARE = "hardware"; + + // taptap的用户ID的外显ID(加密)d + public const string PARAM_OPEN_ID = "open_id"; + + // 网络类型,eg:wifi, mobile + public const string PARAM_NETWORK_TYPE = "network_type"; + + // SDK进程粒度的本地日志 session_id + public const string PARAM_P_SESSION_ID = "p_session_id"; + } +} diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs.meta new file mode 100644 index 0000000..27b5695 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogParamConstants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cef15b6df966846ab9959061ba833aa9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs new file mode 100644 index 0000000..231e1b8 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs @@ -0,0 +1,198 @@ + +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using TapSDK.Core.Internal.Log; +using UnityEngine; +using Newtonsoft.Json; +using TapSDK.Core.Standalone.Internal.Openlog; +using ProtoBuf; + +namespace TapSDK.Core.Standalone.Internal +{ + public abstract class TapOpenlogQueueBase + { + private TapLog log; + private string module; + private string persistentDataPath = Application.persistentDataPath; + private Queue queue = new Queue(); + private TapOpenlogHttpClient httpClient = new TapOpenlogHttpClient(); + private const int MaxEvents = 50; + private const int MaxBatchSize = 200; + private const float SendInterval = 15; + private Timer timer; + private int queueCount => queue.Count; + + protected abstract string GetUrlPath(); + protected abstract string GetEventFilePath(); + + public TapOpenlogQueueBase(string module) + { + this.module = module; + log = new TapLog(module: "Openlog." + module); + // 加载未发送的事件 + LoadStorageLogs(); + SendEventsAsync(); + } + + public void Enqueue(TapOpenlogStoreBean bean) + { + // 将事件添加到队列 + queue.Enqueue(bean); + SaveEvents(); + + // 检查队列大小 + if (queueCount >= MaxEvents) + { + log.Log("队列大小超过最大值 = " + queueCount); + SendEventsAsync(); + log.Log("队列大小超过最大值 end"); + } + else + { + ResetTimer(); + } + } + + public async void SendEventsAsync() + { + if (queueCount == 0) + { + return; + } + var eventsToSend = new List(); + LogGroup logGroup = new LogGroup(); + logGroup.Logs = new List(); + for (int i = 0; i < MaxBatchSize && queueCount > 0; i++) + { + TapOpenlogStoreBean bean = queue.Dequeue(); + eventsToSend.Add(bean); + + Log log = new Log(); + log.Time = (uint)(bean.timestamp / 1000); + log.Contents = new List(); + foreach (var kvp in bean.props) + { + LogContent logContent = new LogContent + { + Key = kvp.Key ?? "", + Value = kvp.Value ?? "" + }; + log.Contents.Add(logContent); + } + logGroup.Logs.Add(log); + } + + byte[] bytes; + using (var stream = new MemoryStream()) + { + Serializer.Serialize(stream, logGroup); + bytes = stream.ToArray(); + } + + var result = await httpClient.Post(GetUrlPath(), content: bytes); + if (!result) + { + foreach (var eventParams in eventsToSend) + { + queue.Enqueue(eventParams); + } + SaveEvents(); + } + else + { + log.Log("SendEvents success"); + SaveEvents(); + if (queueCount > MaxEvents) + { + SendEventsAsync(); + } + } + + } + + private void OnTimerElapsed(object state) + { + timer.Dispose(); + timer = null; + SendEventsAsync(); + } + + private void ResetTimer() + { + if (timer == null) + { + // 设置计时器,15秒后触发一次 + timer = new Timer(OnTimerElapsed, null, TimeSpan.FromSeconds(SendInterval), Timeout.InfiniteTimeSpan); + } + } + + private void LoadStorageLogs() + { + string filePath = Path.Combine(persistentDataPath, GetEventFilePath()); + if (File.Exists(filePath)) + { + string jsonData = File.ReadAllText(filePath); + if (string.IsNullOrEmpty(jsonData)) + { + return; + } + List deserializedData; + try + { + deserializedData = JsonConvert.DeserializeObject>(jsonData); + } + catch (Exception ex) + { + log.Warning($"LoadLogs( FileName : {GetEventFilePath()} ) Exception", ex.ToString()); + File.Delete(filePath); + return; + } + if (deserializedData != null && deserializedData.Count > 0) + { + foreach (var item in deserializedData) + { + queue.Enqueue(item); + } + } + } + log.Log("LoadStorageLogs end, count = " + queue.Count, JsonConvert.SerializeObject(queue.ToList())); + } + + private void SaveEvents() + { + try + { + if (queue == null) + { + return; + } + + var eventList = queue.ToList(); + string jsonData = JsonConvert.SerializeObject(eventList); + + if (string.IsNullOrEmpty(GetEventFilePath())) + { + log.Log("EventFilePath is null or empty"); + return; + } + + string filePath = Path.Combine(persistentDataPath, GetEventFilePath()); + if (string.IsNullOrEmpty(filePath)) + { + return; + } + + File.WriteAllText(filePath, jsonData); + } + catch (Exception ex) + { + log.Warning("SaveEvents Exception" + ex.Message); + } + } + } +} +#endif \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs.meta new file mode 100644 index 0000000..37575d0 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b41c2095375aa4c3bbb590c262e02f6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs new file mode 100644 index 0000000..03ea797 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs @@ -0,0 +1,17 @@ +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN +namespace TapSDK.Core.Standalone.Internal +{ + public class TapOpenlogQueueBusiness : TapOpenlogQueueBase + { + private const string eventFilePath = "TapLogBusiness"; + private const string urlPath = "putrecords/tds/tapsdk"; + + public TapOpenlogQueueBusiness() : base("Business") + { + } + + protected override string GetEventFilePath() => eventFilePath; + protected override string GetUrlPath() => urlPath; + } +} +#endif \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs.meta new file mode 100644 index 0000000..6da68d0 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueBusiness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b7900e0a4c624edfabc2326bdbab953 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs new file mode 100644 index 0000000..81751ab --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs @@ -0,0 +1,15 @@ +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN +namespace TapSDK.Core.Standalone.Internal +{ + public class TapOpenlogQueueTechnology : TapOpenlogQueueBase + { + private const string eventFilePath = "TapLogTechnology"; + private const string urlPath = "putrecords/tds/tapsdk-apm"; + public TapOpenlogQueueTechnology() : base("Technology") + { + } + protected override string GetEventFilePath() => eventFilePath; + protected override string GetUrlPath() => urlPath; + } +} +#endif \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs.meta new file mode 100644 index 0000000..c2113aa --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogQueueTechnology.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c07d949d79d7d4ec893e5bda5e4c141d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs b/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs new file mode 100644 index 0000000..35e8d69 --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs @@ -0,0 +1,190 @@ + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using TapSDK.Core.Internal.Log; +using UnityEngine; + +namespace TapSDK.Core.Standalone.Internal.Openlog +{ + public class TapOpenlogStandalone + { + public static string openid = ""; + + private static Dictionary generalParameter = new Dictionary(); + private static string sessionID = Guid.NewGuid().ToString(); +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN + private static TapOpenlogQueueBusiness businessQueue; + private static TapOpenlogQueueTechnology technologyQueue; +#endif + private string sdkProjectName; + private string sdkProjectVersion; + + private static TapLog log = new TapLog(module: "Openlog"); + public static void Init() + { +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN + businessQueue = new TapOpenlogQueueBusiness(); + technologyQueue = new TapOpenlogQueueTechnology(); +#endif + InitGeneralParameter(); + } + + public TapOpenlogStandalone(string sdkProjectName, string sdkProjectVersion) + { + this.sdkProjectName = sdkProjectName; + this.sdkProjectVersion = sdkProjectVersion; + } + + public void LogBusiness( + string action, + Dictionary properties = null + ) + { + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string tLogId = Guid.NewGuid().ToString(); + + if (properties == null) + { + properties = new Dictionary(); + } + Dictionary props = new Dictionary(properties); + // generalProperties + InflateGeneralProperties(props); + // dynamicProperties + InflateDynamicProperties(props); + + // 该条日志的唯一标识 + props[TapOpenlogParamConstants.PARAM_T_LOG_ID] = tLogId; + // 客户端生成的时间戳,毫秒级 + props[TapOpenlogParamConstants.PARAM_TIMESTAMP] = timestamp.ToString(); + + props["action"] = action; + + TapOpenlogStoreBean bean = new TapOpenlogStoreBean(action, timestamp, tLogId, props); + log.Log("LogBusiness action = " + action + ", sdkProjectName = " + sdkProjectName + " , sdkProjectVersion = " + sdkProjectVersion, bean + "\n" + JsonConvert.SerializeObject(properties)); +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN + businessQueue.Enqueue(bean); +#else + // log.Log($"This Platform 【{Application.platform}】 is not supported for Openlog."); +#endif + } + + public void LogTechnology( + string action, + Dictionary properties = null + ) + { + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string tLogId = Guid.NewGuid().ToString(); + + if (properties == null) + { + properties = new Dictionary(); + } + Dictionary props = new Dictionary(properties); + // generalProperties + InflateGeneralProperties(props); + // dynamicProperties + InflateDynamicProperties(props); + + // 该条日志的唯一标识 + props[TapOpenlogParamConstants.PARAM_T_LOG_ID] = tLogId; + // 客户端生成的时间戳,毫秒级 + props[TapOpenlogParamConstants.PARAM_TIMESTAMP] = timestamp.ToString(); + + props["action"] = action; + + TapOpenlogStoreBean bean = new TapOpenlogStoreBean(action, timestamp, tLogId, props); + + log.Log("LogTechnology action = " + action, bean + "\n" + JsonConvert.SerializeObject(properties)); +#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN + technologyQueue.Enqueue(bean); +#else + // log.Log($"This Platform 【{Application.platform}】 is not supported for Openlog."); +#endif + } + + private static void InitGeneralParameter() + { + // 应用包名 + generalParameter[TapOpenlogParamConstants.PARAM_APP_PACKAGE_NAME] = Application.identifier; + // 应用版本字符串 + generalParameter[TapOpenlogParamConstants.PARAM_APP_VERSION] = Application.version; + // 应用版本(数字) + generalParameter[TapOpenlogParamConstants.PARAM_APP_VERSION_CODE] = ""; + // 固定一个枚举值: TapSDK + generalParameter[TapOpenlogParamConstants.PARAM_PN] = "TapSDK"; + // SDK 产物类型 + generalParameter[TapOpenlogParamConstants.PARAM_TAPSDK_ARTIFACT] = "Unity"; + // SDK 运行平台 + generalParameter[TapOpenlogParamConstants.PARAM_PLATFORM] = "PC"; + // 埋点版本号(预留字段),当前全为1 + generalParameter[TapOpenlogParamConstants.PARAM_TRACK_CODE] = "1"; + // SDK生成的设备一次安装的唯一标识 + generalParameter[TapOpenlogParamConstants.PARAM_INSTALL_UUID] = Identity.InstallationId; + // 设备品牌,eg: Xiaomi + generalParameter[TapOpenlogParamConstants.PARAM_DV] = ""; + // 设备品牌型号,eg:21051182C + generalParameter[TapOpenlogParamConstants.PARAM_MD] = SystemInfo.deviceModel; + // 设备CPU型号,eg:arm64-v8a + generalParameter[TapOpenlogParamConstants.PARAM_CPU] = ""; + // 支持 CPU 架构,eg:arm64-v8a + generalParameter[TapOpenlogParamConstants.PARAM_CPU_ABIS] = ""; + // 设备操作系统 + generalParameter[TapOpenlogParamConstants.PARAM_OS] = SystemInfo.operatingSystemFamily.ToString(); + // 设备操作系统版本 + generalParameter[TapOpenlogParamConstants.PARAM_SV] = SystemInfo.operatingSystem; + // 物理设备真实屏幕分辨率宽 + generalParameter[TapOpenlogParamConstants.PARAM_WIDTH] = Screen.currentResolution.width.ToString(); + // 物理设备真实屏幕分辨率高 + generalParameter[TapOpenlogParamConstants.PARAM_HEIGHT] = Screen.currentResolution.height.ToString(); + // 设备总存储空间(磁盘),单位B + generalParameter[TapOpenlogParamConstants.PARAM_TOTAL_ROM] = ""; + // 设备总内存,单位B + generalParameter[TapOpenlogParamConstants.PARAM_TOTAL_RAM] = DeviceInfo.RAM; + // 芯片型号,eg:Qualcomm Technologies, Inc SM7250 + generalParameter[TapOpenlogParamConstants.PARAM_HARDWARE] = SystemInfo.processorType; + // SDK进程粒度的本地日志 session_id + generalParameter[TapOpenlogParamConstants.PARAM_P_SESSION_ID] = sessionID; + } + + private void InflateGeneralProperties(Dictionary props) + { + if (generalParameter != null) + { + foreach (KeyValuePair kv in generalParameter) + { + props[kv.Key] = kv.Value; + } + } + } + + private void InflateDynamicProperties(Dictionary props) + { + + // 客户端时区,eg:Asia/Shanghai + props[TapOpenlogParamConstants.PARAM_TIMEZONE] = ""; + // SDK接入项目具体模块枚举值 + props[TapOpenlogParamConstants.PARAM_TAPSDK_PROJECT] = sdkProjectName; + // SDK 模块版本号 + props[TapOpenlogParamConstants.PARAM_TAPSDK_VERSION] = sdkProjectVersion; + // SDK设置的地区,例如 zh_CN + props[TapOpenlogParamConstants.PARAM_SDK_LOCALE] = Tracker.getServerLanguage(); + // 游戏账号 ID(非角色 ID) + props[TapOpenlogParamConstants.PARAM_GAME_USER_ID] = TapCoreStandalone.User.Id; + // SDK生成的设备全局唯一标识 + props[TapOpenlogParamConstants.PARAM_GID] = ""; + // SDK生成的设备唯一标识 + props[TapOpenlogParamConstants.PARAM_DEVICE_ID] = SystemInfo.deviceUniqueIdentifier; + // 设备可用存储空间(磁盘),单位B + props[TapOpenlogParamConstants.PARAM_ROM] = "0"; + // 设备可用内存,单位B + props[TapOpenlogParamConstants.PARAM_RAM] = "0"; + // taptap的用户ID的外显ID(加密) + props[TapOpenlogParamConstants.PARAM_OPEN_ID] = openid; + // 网络类型,eg:wifi, mobile + props[TapOpenlogParamConstants.PARAM_NETWORK_TYPE] = ""; + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs.meta b/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs.meta new file mode 100644 index 0000000..bbc533f --- /dev/null +++ b/Standalone/Runtime/Internal/Openlog/TapOpenlogStandalone.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ca3a4d34396f4bd6946aa219459ad60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/PlayRecorder.cs b/Standalone/Runtime/Internal/PlayRecorder.cs new file mode 100644 index 0000000..acb3d58 --- /dev/null +++ b/Standalone/Runtime/Internal/PlayRecorder.cs @@ -0,0 +1,53 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using UnityEngine; + +namespace TapSDK.Core.Standalone.Internal { + public class PlayRecorder { + internal static readonly string PLAYED_DURATION_KEY = "tapdb_played_duration"; + + /// + /// 记录间隔 + /// + private const int RECORD_INTERVAL = 2 * 1000; + + private CancellationTokenSource cts; + + /// + /// 启动记录 + /// + public async void Start() { + if (cts != null && !cts.IsCancellationRequested) { + cts.Cancel(); + } + + cts = new CancellationTokenSource(); + while (Application.isPlaying) { + try { + await Task.Delay(RECORD_INTERVAL, cts.Token); + } catch (TaskCanceledException) { + break; + } + + // 保存用户游玩时长 + TapCoreStandalone.Prefs.AddOrUpdate(PLAYED_DURATION_KEY, + 2L, + (k, v) => (long)v + 2); + } + } + + /// + /// 结束记录并上报 + /// + public void Stop() { + cts?.Cancel(); + if (TapCoreStandalone.Prefs.TryRemove(PLAYED_DURATION_KEY, out long duration)) { + Dictionary props = new Dictionary { + { "duration", duration } + }; + TapCoreStandalone.Tracker.TrackEvent("play_game", props, true); + } + } + } +} diff --git a/Standalone/Runtime/Internal/PlayRecorder.cs.meta b/Standalone/Runtime/Internal/PlayRecorder.cs.meta new file mode 100644 index 0000000..64a1eaf --- /dev/null +++ b/Standalone/Runtime/Internal/PlayRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c289811a1dbcd41be9707de8e51a0e21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Prefs.cs b/Standalone/Runtime/Internal/Prefs.cs new file mode 100644 index 0000000..6a426aa --- /dev/null +++ b/Standalone/Runtime/Internal/Prefs.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading; +using System.Linq; +using System.IO; +using UnityEngine; +using TapSDK.Core; + +namespace TapSDK.Core.Standalone.Internal { + public class Prefs { + internal static readonly string PERSISTENT_FILE_NAME = "tapdb_storage_v2"; + + private string persistentFilePath; + + private readonly ConcurrentDictionary data; + + private readonly Thread persistThread; + + private readonly AutoResetEvent persistEvent; + + public Prefs() { + persistentFilePath = Path.Combine(Application.persistentDataPath, PERSISTENT_FILE_NAME); + if (File.Exists(persistentFilePath)) { + try { + string json = File.ReadAllText(persistentFilePath); + Dictionary jsonData = Json.Deserialize(json) as Dictionary; + ConcurrentDictionary data = new ConcurrentDictionary(jsonData); + } catch (Exception e) { + TapLogger.Error(e.Message); + File.Delete(persistentFilePath); + } + } + if (data == null) { + data = new ConcurrentDictionary(); + } + persistEvent = new AutoResetEvent(false); + persistThread = new Thread(PersistProc) { + IsBackground = true + }; + persistThread.Start(); + } + + public T Get(string key) { + if (data.TryGetValue(key, out object val)) { + return (T)val; + } + return default; + } + + public void Set(string key, T value) { + data[key] = value; + persistEvent.Set(); + } + + public bool TryRemove(string key, out T val) { + if (data.TryRemove(key, out object v)) { + val = (T)v; + persistEvent.Set(); + return true; + } + val = default; + return false; + } + + public void AddOrUpdate(string key, object addValue, Func updateValueFactory) { + data.AddOrUpdate(key, addValue, updateValueFactory); + persistEvent.Set(); + } + + private void PersistProc() { + while (true) { + persistEvent.WaitOne(); + try { + Dictionary dict = data.ToArray() + .ToDictionary(kv => kv.Key, kv => kv.Value); + string json = Json.Serialize(dict); + File.WriteAllText(persistentFilePath, json); + } catch (Exception e) { + TapLogger.Error(e.Message); + } + } + } + } +} diff --git a/Standalone/Runtime/Internal/Prefs.cs.meta b/Standalone/Runtime/Internal/Prefs.cs.meta new file mode 100644 index 0000000..4d29d52 --- /dev/null +++ b/Standalone/Runtime/Internal/Prefs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9465a7fca8d2451b94b37902d875c5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Tracker.cs b/Standalone/Runtime/Internal/Tracker.cs new file mode 100644 index 0000000..2d2cd8f --- /dev/null +++ b/Standalone/Runtime/Internal/Tracker.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using TapSDK.Core.Standalone; +using System.Threading.Tasks; +using UnityEngine; +using TapSDK.Core.Internal.Utils; + +namespace TapSDK.Core.Standalone.Internal { + public class Tracker { + + private Dictionary customProps; + + private Dictionary basicProps; + private Dictionary commonProps; + + private EventSender sender; + private IDynamicProperties dynamicPropsDelegate; + + private static string session_uuid = generateUUID(); + + public void Init() { + basicProps = new Dictionary(); + commonProps = new Dictionary(); + + var coreOptions = TapCoreStandalone.coreOptions; + customProps = Json.Deserialize(coreOptions.propertiesJson) as Dictionary; + sender = new EventSender(); + + InitBasicProps(); + if (TapCoreStandalone.enableAutoEvent) { + Dictionary props = new Dictionary(basicProps); + TrackEvent(Constants.DEVICE_LOGIN, props, true); + } + } + + public void AddCommonProperty(string key, object value) { + commonProps[key] = value; + } + + public void AddCommon(Dictionary properties) { + foreach (KeyValuePair kv in properties) { + commonProps[kv.Key] = kv.Value; + } + } + public void ClearCommonProperty(string key) { + commonProps.Remove(key); + } + public void ClearCommonProperties(string[] keys) { + foreach (string key in keys) { + commonProps.Remove(key); + } + } + public void ClearAllCommonProperties() { + commonProps.Clear(); + } + + public void RegisterDynamicPropsDelegate(IDynamicProperties dynamicPropsDelegate) { + this.dynamicPropsDelegate = dynamicPropsDelegate; + } + + public void LogPurchasedEvent(string orderID, string productName, Int64 amount, string currencyType, string paymentMethod, string properties){ + var prop = Json.Deserialize(properties) as Dictionary; + + var data = new Dictionary { + { "order_id", orderID }, + { "product", productName }, + { "amount", amount }, + { "currency_type", currencyType }, + { "payment", paymentMethod } + }; + if (prop != null) { + foreach (KeyValuePair kv in prop) { + data[kv.Key] = kv.Value; + } + } + TrackEvent("charge", data); + } + + /// + /// 上报事件 + /// + /// + /// + /// 是否为自动事件 + public void TrackEvent(string name, Dictionary properties = null, bool isAutomationlly = false) { + + Dictionary props = new Dictionary(basicProps); + + if (commonProps != null) { + foreach (KeyValuePair kv in commonProps) { + props[kv.Key] = kv.Value; + } + } + + Dictionary dynamicProps = dynamicPropsDelegate?.GetDynamicProperties(); + TapLogger.Debug("dynamicProps: " + dynamicProps); + if (dynamicProps != null) { + foreach (KeyValuePair kv in dynamicProps) { + props[kv.Key] = kv.Value; + } + } + + if (name == Constants.DEVICE_LOGIN) { // Device login 事件带上初始化时的自定义属性 + TapLogger.Debug("customProps: " + customProps); + if (customProps != null) { + foreach (KeyValuePair kv in customProps) { + props[kv.Key] = kv.Value; + } + } + } + + props["t_log_id"] = generateUUID(); + // 时间戳,毫秒级 + props["timestamp"] = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + var open_id = OpenID; + if (!string.IsNullOrWhiteSpace(open_id)) { + props["open_id"] = open_id; + } + + TapLogger.Debug("properties: " + properties); + if (properties != null) { + foreach (KeyValuePair kv in properties) { + props[kv.Key] = kv.Value; + } + } + + props["is_automatically_log"] = isAutomationlly ? "true" : "false"; + + var language = getServerLanguage(); + props["sdk_locale"] = language; + props["lang_system"] = DeviceInfo.GetLanguage(); + + Dictionary data = new Dictionary { + { "client_id", TapCoreStandalone.coreOptions.clientId }, + { "type", "track" }, + { "name", name }, + { "device_id", Identity.DeviceId }, + { "properties", props }, + }; + if (!string.IsNullOrWhiteSpace(TapCoreStandalone.User.Id)) { + data["user_id"] = TapCoreStandalone.User.Id; + } + + sender.Send(data); + } + + /// + /// 上报设备属性变化 + /// + /// + /// + public void TrackDeviceProperties(string type, Dictionary properties) { + if (string.IsNullOrWhiteSpace(Identity.DeviceId)) { + TapLogger.Error("DeviceId is NULL."); + return; + } + + Dictionary baseProps = new Dictionary { + { "device_id", Identity.DeviceId } + }; + _ = TrackPropertiesAsync(type, baseProps, properties); + } + + /// + /// 上报玩家属性变化 + /// + public void TrackUserProperties(string type, Dictionary properties) { + string userId = TapCoreStandalone.User.Id; + if (string.IsNullOrWhiteSpace(userId)) { + TapLogger.Error("UserId is NULL."); + return; + } + + Dictionary baseProps = new Dictionary { + { "user_id", userId } + }; + _ = TrackPropertiesAsync(type, baseProps, properties); + } + + private Task TrackPropertiesAsync(string type, + Dictionary basicProps, Dictionary properties) + { + if (!IsInitialized) { + return Task.CompletedTask; + } + + if (properties == null) { + properties = new Dictionary(); + } + properties["sdk_version"] = TapTapSDK.Version; + + Dictionary data = new Dictionary(basicProps) { + { "client_id", TapCoreStandalone.coreOptions.clientId }, + { "type", type }, + { "properties", properties } + }; + + sender.Send(data); + return Task.CompletedTask; + } + + private void InitBasicProps() { + DeviceInfo.GetMacAddress(out string macList, out string firstMac); + basicProps = new Dictionary { + { "os", OS }, + { "md", SystemInfo.deviceModel }, + { "sv", SystemInfo.operatingSystem }, + { "pn", "TapSDK" }, + { "tapsdk_project", "TapSDKCore" }, + { "session_uuid", session_uuid }, + { "install_uuid", Identity.InstallationId }, + { "persist_uuid", Identity.PersistentId }, + { "ram", DeviceInfo.RAM }, + { "rom", DeviceInfo.ROM }, + { "width", Screen.currentResolution.width }, + { "height", Screen.currentResolution.height }, + { "provider", "unknown" }, + { "app_version", TapCoreStandalone.coreOptions.gameVersion ?? Application.version }, + { "sdk_version", TapTapSDK.Version }, + { "network_type", Network }, + { "channel", TapCoreStandalone.coreOptions.channel }, + { "mac_list", macList }, + { "first_mac", firstMac }, + { "device_id5", DeviceInfo.GetLaunchUniqueID() } + }; + } + private string OS { + get { + switch (SystemInfo.operatingSystemFamily) { + case OperatingSystemFamily.Windows: + return "Windows"; + case OperatingSystemFamily.MacOSX: + return "Mac"; + case OperatingSystemFamily.Linux: + return "Linux"; + default: + return "Unknown"; + } + } + } + + public static string getServerLanguage() { + // 将 TapCoreStandalone.coreOptions.preferredLanguage 转成 zh_TW/en/zh_CN/en_GB/jp/fil 等格式 + switch (TapCoreStandalone.coreOptions.preferredLanguage) { + case TapTapLanguageType.zh_Hans: + return "zh_CN"; + case TapTapLanguageType.zh_Hant: + return "zh_TW"; + case TapTapLanguageType.en: + return "en_US"; + case TapTapLanguageType.ja: + return "ja_JP"; + case TapTapLanguageType.ko: + return "ko_KR"; + case TapTapLanguageType.th: + return "th_TH"; + case TapTapLanguageType.id: + return "id_ID"; + case TapTapLanguageType.de: + return "de"; + case TapTapLanguageType.es: + return "es_ES"; + case TapTapLanguageType.fr: + return "fr"; + case TapTapLanguageType.pt: + return "pt_PT"; + case TapTapLanguageType.ru: + return "ru"; + case TapTapLanguageType.tr: + return "tr"; + case TapTapLanguageType.vi: + return "vi_VN"; + default: + // 默认cn返回简体中文,Overseas返回英文 + return TapCoreStandalone.coreOptions.region == TapTapRegionType.CN ? "zh_CN" : "en_US"; + } + } + + private string Network { + get { + switch (Application.internetReachability) { + case NetworkReachability.ReachableViaCarrierDataNetwork: + return "3"; + case NetworkReachability.ReachableViaLocalAreaNetwork: + return "2"; + default: + return "Unknown"; + } + } + } + + private bool IsInitialized { + get { + if (string.IsNullOrWhiteSpace(TapCoreStandalone.coreOptions.clientId)) { + TapLogger.Error("MUST be initialized."); + return false; + } + return true; + } + } + + private static string generateUUID() { + return Guid.NewGuid().ToString(); + } + + private static string OpenID { + get { + IOpenIDProvider provider = BridgeUtils.CreateBridgeImplementation(typeof(IOpenIDProvider), + "TapSDK.Login") as IOpenIDProvider; + return provider?.GetOpenID(); + } + } + + public interface IDynamicProperties { + Dictionary GetDynamicProperties(); + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Tracker.cs.meta b/Standalone/Runtime/Internal/Tracker.cs.meta new file mode 100644 index 0000000..e37f2ba --- /dev/null +++ b/Standalone/Runtime/Internal/Tracker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed9eafce641cf411196eb3308eceef1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/User.cs b/Standalone/Runtime/Internal/User.cs new file mode 100644 index 0000000..aa00607 --- /dev/null +++ b/Standalone/Runtime/Internal/User.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +namespace TapSDK.Core.Standalone.Internal { + public class User { + internal static readonly string USER_ID_KEY = "tapdb_played_duration_user_id"; + + internal string Id { + get => id; + set { + id = value; + TapCoreStandalone.Prefs.Set(USER_ID_KEY, id); + } + } + + private string id; + + private readonly PlayRecorder playRecorder; + + public User() { + playRecorder = new PlayRecorder(); + } + + public void Login(string userId, Dictionary props = null) { + // 先执行旧用户登出逻辑 + Id = TapCoreStandalone.Prefs.Get(USER_ID_KEY); + if (!string.IsNullOrWhiteSpace(Id)) { + Logout(); + } + + // 再执行新用户登录逻辑 + Id = userId; + + if (TapCoreStandalone.enableAutoEvent) { + TapCoreStandalone.Tracker.TrackEvent(Constants.USER_LOGIN, props, true); + } + + Dictionary updateProps = new Dictionary { + { "has_user", true }, + }; + TapCoreStandalone.Tracker.TrackDeviceProperties(Constants.PROPERTY_UPDATE_TYPE, updateProps); + + playRecorder.Start(); + } + + public void Logout() { + playRecorder.Stop(); + + Id = null; + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/User.cs.meta b/Standalone/Runtime/Internal/User.cs.meta new file mode 100644 index 0000000..4c9008c --- /dev/null +++ b/Standalone/Runtime/Internal/User.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 910b81931513342b08384bc8ade66afb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Utils.meta b/Standalone/Runtime/Internal/Utils.meta new file mode 100644 index 0000000..8257dc5 --- /dev/null +++ b/Standalone/Runtime/Internal/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 784d8481ab7574753a09c8c392b59d06 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs b/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs new file mode 100644 index 0000000..65b145c --- /dev/null +++ b/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs @@ -0,0 +1,28 @@ +// #if UNITY_EDITOR || (!UNITY_ANDROID && !UNITY_IOS) +// using Google.Protobuf; + +// namespace TapSDK.Core.Standalone.Internal.Utils +// { + +// public class TapProtoBufffer +// { +// public static byte[] Serialize(IMessage message) +// { +// return message.ToByteArray(); +// } + +// public static T DeSerialize(byte[] packet) where T : IMessage, new() +// { +// IMessage message = new T(); +// try +// { +// return (T)message.Descriptor.Parser.ParseFrom(packet); +// } +// catch (System.Exception e) +// { +// throw; +// } +// } +// } +// } +// #endif \ No newline at end of file diff --git a/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs.meta b/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs.meta new file mode 100644 index 0000000..2df270e --- /dev/null +++ b/Standalone/Runtime/Internal/Utils/TapProtoBufffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7f3dd7d1657c46acb0a8bb6b62bfdf6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/service.meta b/Standalone/Runtime/Internal/service.meta new file mode 100644 index 0000000..161cd9b --- /dev/null +++ b/Standalone/Runtime/Internal/service.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 92adce826e16c4263aa1d3a4e5664306 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Internal/service/ITapLoginService.cs b/Standalone/Runtime/Internal/service/ITapLoginService.cs new file mode 100644 index 0000000..cf0fbf4 --- /dev/null +++ b/Standalone/Runtime/Internal/service/ITapLoginService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace TapSDK.Core.Standalone.Internal.Service +{ + public interface ITapLoginService + { + string ObtainAuthorizationAsync(string url, string method); + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Internal/service/ITapLoginService.cs.meta b/Standalone/Runtime/Internal/service/ITapLoginService.cs.meta new file mode 100644 index 0000000..d0680aa --- /dev/null +++ b/Standalone/Runtime/Internal/service/ITapLoginService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53ad0b57811aa4067a6bbdbc700ddd63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Public.meta b/Standalone/Runtime/Public.meta new file mode 100644 index 0000000..68b97c3 --- /dev/null +++ b/Standalone/Runtime/Public.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75cc82e9a12884d5c9ac299e6695f804 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Public/EventManager.cs b/Standalone/Runtime/Public/EventManager.cs new file mode 100644 index 0000000..e37b018 --- /dev/null +++ b/Standalone/Runtime/Public/EventManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using TapSDK.UI; + +namespace TapSDK.Core.Standalone +{ + public sealed class EventManager : Singleton + { + private Dictionary> eventRegistries = new Dictionary>(); + + public static void AddListener(string eventName, Action listener) { + if (listener == null) return; + if (string.IsNullOrEmpty(eventName)) return; + Action thisEvent; + if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) { + thisEvent += listener; + Instance.eventRegistries[eventName] = thisEvent; + } else { + thisEvent += listener; + Instance.eventRegistries.Add(eventName, thisEvent); + } + } + + public static void RemoveListener(string eventName, Action listener) { + if (listener == null) return; + if (string.IsNullOrEmpty(eventName)) return; + Action thisEvent; + if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) { + thisEvent -= listener; + Instance.eventRegistries[eventName] = thisEvent; + } + } + + public static void TriggerEvent(string eventName, object message) { + Action thisEvent = null; + if (Instance.eventRegistries.TryGetValue(eventName, out thisEvent)) { + thisEvent.Invoke(message); + } + } + } +} \ No newline at end of file diff --git a/Standalone/Runtime/Public/EventManager.cs.meta b/Standalone/Runtime/Public/EventManager.cs.meta new file mode 100644 index 0000000..aa862e2 --- /dev/null +++ b/Standalone/Runtime/Public/EventManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95f6670809a3746c3b679b19441b1f6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Public/TapCoreStandalone.cs b/Standalone/Runtime/Public/TapCoreStandalone.cs new file mode 100644 index 0000000..8bbcc4e --- /dev/null +++ b/Standalone/Runtime/Public/TapCoreStandalone.cs @@ -0,0 +1,186 @@ +using TapSDK.Core.Internal; +using UnityEngine; +using TapSDK.Core.Standalone.Internal; +using System.Collections.Generic; +using UnityEditor; +using System.IO; +using TapSDK.Core.Internal.Utils; +using TapSDK.Core.Standalone.Internal.Openlog; +using TapSDK.Core.Internal.Log; +using TapSDK.Core.Standalone.Internal.Http; +using Newtonsoft.Json; +using TapSDK.Core.Standalone.Internal.Bean; + +namespace TapSDK.Core.Standalone +{ + /// + /// Represents the standalone implementation of the TapCore SDK. + /// + public class TapCoreStandalone : ITapCorePlatform + { + internal static Prefs Prefs; + internal static Tracker Tracker; + internal static User User; + internal static TapTapSdkOptions coreOptions; + public static bool isRnd = false; + internal static bool enableAutoEvent = true; + + internal static TapGatekeeper gatekeeperData = new TapGatekeeper(); + + private readonly TapHttp tapHttp = TapHttp.NewBuilder("TapSDKCore", TapTapSDK.Version).Build(); + + /// + /// Initializes a new instance of the class. + /// + public TapCoreStandalone() + { + Debug.Log("TapCoStandalone constructor"); + // Instantiate modules + Prefs = new Prefs(); + Tracker = new Tracker(); + User = new User(); + TapLoom.Initialize(); + } + + private static void SetRND(bool isRnd) + { + TapLog.Log("SetRND called = " + isRnd); + TapCoreStandalone.isRnd = isRnd; + } + + /// + /// Initializes the TapCore SDK with the specified options. + /// + /// The TapCore SDK options. + public void Init(TapTapSdkOptions options) + { + Init(options, null); + } + + /// + /// Initializes the TapCore SDK with the specified core options and additional options. + /// + /// The TapCore SDK core options. + /// Additional TapCore SDK options. + public void Init(TapTapSdkOptions coreOption, TapTapSdkBaseOptions[] otherOptions) + { + TapLog.Log("SDK inited with other options + " + coreOption.ToString() + coreOption.ToString()); + coreOptions = coreOption; + + var path = Path.Combine(Application.persistentDataPath, Constants.ClientSettingsFileName); + if (File.Exists(path)) + { + var clientSettings = File.ReadAllText(path); + TapLog.Log("本地 clientSettings: " + clientSettings); + try + { + TapGatekeeper tapGatekeeper = JsonConvert.DeserializeObject(clientSettings); + SetAutoEvent(tapGatekeeper); + gatekeeperData = tapGatekeeper; + } + catch (System.Exception e) + { + TapLog.Warning("TriggerEvent error: " + e.Message); + } + } + + Tracker.Init(); + + TapOpenlogStandalone.Init(); + requestClientSetting(); + } + + public void UpdateLanguage(TapTapLanguageType language) + { + if (coreOptions == null) + { + Debug.Log("coreOptions is null"); + return; + } + TapLog.Log("UpdateLanguage called with language: " + language); + coreOptions.preferredLanguage = language; + } + + public static string getGatekeeperConfigUrl(string key) + { + if (gatekeeperData != null) + { + var urlsData = gatekeeperData.Urls; + if (urlsData != null && urlsData.ContainsKey(key)) + { + var keyData = urlsData[key]; + if (keyData != null) + { + return keyData.Browser; + } + } + } + return null; + } + + private void requestClientSetting() + { + // 使用 httpclient 请求 /sdk-core/v1/gatekeeper 获取配置 +#if UNITY_EDITOR + var bundleIdentifier = PlayerSettings.applicationIdentifier; +#else + var bundleIdentifier = Application.identifier; +#endif + var path = "sdk-core/v1/gatekeeper"; + var body = new Dictionary { + { "platform", "pc" }, + { "bundle_id", bundleIdentifier } + }; + + tapHttp.PostJson( + url: path, + json: body, + onSuccess: (data) => + { + SetAutoEvent(data); + gatekeeperData = data; + // 把 data 存储在本地 + saveClientSettings(data); + // 发通知 + EventManager.TriggerEvent(Constants.ClientSettingsEventKey, data); + }, + onFailure: (error) => + { + if (error is TapHttpServerException se) + { + if (TapHttpErrorConstants.ERROR_INVALID_CLIENT.Equals(se.ErrorData.Error)) + { + TapLog.Error("Init Failed", se.ErrorData.ErrorDescription); + TapMessage.ShowMessage(se.ErrorData.Msg, TapMessage.Position.bottom, TapMessage.Time.twoSecond); + } + } + } + ); + } + + private void saveClientSettings(TapGatekeeper settings) + { + string json = JsonConvert.SerializeObject(settings); + Debug.Log("saveClientSettings: " + json); + File.WriteAllText(Path.Combine(Application.persistentDataPath, Constants.ClientSettingsFileName), json); + } + + private void SetAutoEvent(TapGatekeeper gatekeeper) + { + if (gatekeeper != null) + { + var switchData = gatekeeper.Switch; + if (switchData != null) + { + enableAutoEvent = switchData.AutoEvent; + } + } + Debug.Log("SetAutoEvent enableAutoEvent is: " + enableAutoEvent); + } + } + + public interface IOpenIDProvider + { + string GetOpenID(); + } +} diff --git a/Standalone/Runtime/Public/TapCoreStandalone.cs.meta b/Standalone/Runtime/Public/TapCoreStandalone.cs.meta new file mode 100644 index 0000000..d5874a2 --- /dev/null +++ b/Standalone/Runtime/Public/TapCoreStandalone.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e53020dc505c04b53a9fe5365cae8114 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/Public/TapEventStandalone.cs b/Standalone/Runtime/Public/TapEventStandalone.cs new file mode 100644 index 0000000..36a774a --- /dev/null +++ b/Standalone/Runtime/Public/TapEventStandalone.cs @@ -0,0 +1,281 @@ +using System; +using TapSDK.Core.Internal; +using TapSDK.Core.Standalone.Internal; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace TapSDK.Core.Standalone +{ + /// + /// Represents the standalone implementation of the Tap event. + /// + public class TapEventStandalone : ITapEventPlatform + { + private readonly Tracker Tracker = TapCoreStandalone.Tracker; + private readonly User User = TapCoreStandalone.User; + + /// + /// Sets the user ID for tracking events. + /// + /// The user ID to set. + public void SetUserID(string userID) + { + SetUserID(userID, null); + } + + /// + /// Sets the user ID and additional properties for tracking events. + /// + /// The user ID to set. + /// Additional properties to associate with the user. + public void SetUserID(string userID, string properties) + { + if (!IsValidUserID(userID)) + { + TapLogger.Error("Invalid user ID, length should be 1-160 and only contains a-zA-Z0-9_+/=.,:"); + return; + } + + Dictionary prop = Json.Deserialize(properties) as Dictionary; + User.Login(userID, filterProperties(prop)); + } + + /// + /// Clears the current user. + /// + public void ClearUser() + { + User.Logout(); + } + + /// + /// Gets the device ID. + /// + /// The device ID. + public string GetDeviceId() + { + return Identity.DeviceId; + } + + /// + /// Logs an event with the specified name and properties. + /// + /// The name of the event. + /// Additional properties to associate with the event. + public void LogEvent(string name, string properties) + { + // name 长度256非空,不符合的丢事件,打log + if (!checkLength(name)) { + Debug.LogError(name + " Event name length should be less than or equal to 256 characters."); + return; + } + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackEvent(name, filterProperties(prop)); + } + + /// + /// Tracks device initialization with the specified properties. + /// + /// Additional properties to associate with the device initialization. + public void DeviceInitialize(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackDeviceProperties(Constants.PROPERTY_INITIALIZE_TYPE, filterProperties(prop)); + } + + /// + /// Tracks device update with the specified properties. + /// + /// Additional properties to associate with the device update. + public void DeviceUpdate(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackDeviceProperties(Constants.PROPERTY_UPDATE_TYPE, filterProperties(prop)); + } + + /// + /// Tracks device addition with the specified properties. + /// + /// Additional properties to associate with the device addition. + public void DeviceAdd(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackDeviceProperties(Constants.PROPERTY_ADD_TYPE, filterProperties(prop)); + } + + /// + /// Tracks user initialization with the specified properties. + /// + /// Additional properties to associate with the user initialization. + public void UserInitialize(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackUserProperties(Constants.PROPERTY_INITIALIZE_TYPE, filterProperties(prop)); + } + + /// + /// Tracks user update with the specified properties. + /// + /// Additional properties to associate with the user update. + public void UserUpdate(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackUserProperties(Constants.PROPERTY_UPDATE_TYPE, filterProperties(prop)); + } + + /// + /// Tracks user addition with the specified properties. + /// + /// Additional properties to associate with the user addition. + public void UserAdd(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.TrackUserProperties(Constants.PROPERTY_ADD_TYPE, filterProperties(prop)); + } + + /// + /// Adds a common property with the specified key and value. + /// + /// The key of the common property. + /// The value of the common property. + public void AddCommonProperty(string key, string value) + { + if (!checkLength(key)){ + Debug.LogError(key + " Property key length should be less than or equal to 256 characters."); + return; + } + if (!checkLength(value)){ + Debug.LogError(value + " Property value length should be less than or equal to 256 characters."); + return; + } + Tracker.AddCommonProperty(key, value); + } + + /// + /// Adds common properties with the specified JSON string. + /// + /// The JSON string containing the common properties. + public void AddCommon(string properties) + { + Dictionary prop = Json.Deserialize(properties) as Dictionary; + Tracker.AddCommon(filterProperties(prop)); + } + + /// + /// Clears the common property with the specified key. + /// + /// The key of the common property to clear. + public void ClearCommonProperty(string key) + { + Tracker.ClearCommonProperty(key); + } + + /// + /// Clears the common properties with the specified keys. + /// + /// The keys of the common properties to clear. + public void ClearCommonProperties(string[] keys) + { + Tracker.ClearCommonProperties(keys); + } + + /// + /// Clears all common properties. + /// + public void ClearAllCommonProperties() + { + Tracker.ClearAllCommonProperties(); + } + + /// + /// Logs a charge event with the specified details and properties. + /// + /// The ID of the order. + /// The name of the product. + /// The amount of the charge. + /// The currency type of the charge. + /// The payment method used for the charge. + /// Additional properties to associate with the charge event. + public void LogChargeEvent(string orderID, string productName, long amount, string currencyType, string paymentMethod, string properties) + { + if (amount <= 0 || amount > 100000000000) { + UnityEngine.Debug.LogError(amount + " is invalid, amount should be in range (0, 100000000000]"); + return; + } + Tracker.LogPurchasedEvent(orderID, productName, amount, currencyType, paymentMethod, properties); + } + + /// + /// Registers a callback function for retrieving dynamic properties. + /// + /// The callback function that returns a JSON string containing the dynamic properties. + public void RegisterDynamicProperties(Func callback) + { + DynamicProperties dynamicProperties = new DynamicProperties(callback); + Tracker.RegisterDynamicPropsDelegate(dynamicProperties); + } + + /// + /// Represents the implementation of dynamic properties for the Tap event platform. + /// + public class DynamicProperties : Tracker.IDynamicProperties + { + readonly Func callback; + + /// + /// Initializes a new instance of the class with the specified callback function. + /// + /// The callback function that returns a JSON string containing the dynamic properties. + public DynamicProperties(Func callback) + { + this.callback = callback; + } + + /// + /// Gets the dynamic properties. + /// + /// A dictionary containing the dynamic properties. + public Dictionary GetDynamicProperties() + { + var jsonString = callback(); + return Json.Deserialize(jsonString) as Dictionary; + } + } + + private bool checkLength(string value){ + var maxLength = 256; + if (value.Length <= 0 || value.Length > maxLength){ + return false; + } + return true; + } + + private bool IsValidUserID(string userID) + { + string pattern = @"^[a-zA-Z0-9_+/=.,:]{1,160}$"; + Regex regex = new Regex(pattern); + return regex.IsMatch(userID); + } + + private Dictionary filterProperties(Dictionary properties) + { + Dictionary filteredProperties = new Dictionary(); + foreach (var property in properties) + { + if (property.Key.Length <= 0 || property.Key.Length > 256) + { + Debug.Log(property.Key + " Property key length should be more then 0 and less than or equal to 256 characters."); + continue; + } + if (property.Value.ToString().Length > 256) + { + Debug.Log(property.Value + " Property value length should be less than or equal to 256 characters."); + continue; + } + filteredProperties.Add(property.Key, property.Value); + } + return filteredProperties; + } + } +} diff --git a/Standalone/Runtime/Public/TapEventStandalone.cs.meta b/Standalone/Runtime/Public/TapEventStandalone.cs.meta new file mode 100644 index 0000000..1675c55 --- /dev/null +++ b/Standalone/Runtime/Public/TapEventStandalone.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6287ed7f2e5a41e69024cb87cef792c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef b/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef new file mode 100644 index 0000000..319082a --- /dev/null +++ b/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef @@ -0,0 +1,21 @@ +{ + "name": "TapSDK.Core.Standalone.Runtime", + "references": [ + "GUID:7d5ef2062f3704e1ab74aac0e4d5a1a7" + ], + "includePlatforms": [ + "Editor", + "LinuxStandalone64", + "macOSStandalone", + "WindowsStandalone32", + "WindowsStandalone64" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef.meta b/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef.meta new file mode 100644 index 0000000..e30f454 --- /dev/null +++ b/Standalone/Runtime/TapSDK.Core.Standalone.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cdf1346592073467a860c3effd9679d4 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/link.xml b/link.xml new file mode 100644 index 0000000..d46dc3d --- /dev/null +++ b/link.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/link.xml.meta b/link.xml.meta new file mode 100644 index 0000000..a40d7d0 --- /dev/null +++ b/link.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 10ec9c4261b764ec0a94ec236fdabef6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..2df2a05 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.taptap.sdk.core", + "displayName": "TapTapSDK Core", + "description": "TapTapSDK Core", + "version": "4.4.0", + "unity": "2019.4", + "license": "MIT", + "dependencies": { + "com.google.external-dependency-manager": "1.2.179" + } +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..4f30221 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9b6954ad3044f4d79b276bb75392e04a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: