react-native-firework-sdk 2.0.0-beta.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +5 -3
- package/android/src/main/java/com/fireworksdk/bridge/components/videofeed/StoryBlockFragment.kt +129 -0
- package/android/src/main/java/com/fireworksdk/bridge/components/videofeed/StoryBlockFrameLayout.kt +84 -0
- package/android/src/main/java/com/fireworksdk/bridge/models/FWEventName.kt +2 -1
- package/android/src/main/java/com/fireworksdk/bridge/models/FWProductInfoViewConfiguration.kt +18 -5
- package/android/src/main/java/com/fireworksdk/bridge/models/FWProductInfoViewConfigurationDeserializer.kt +33 -9
- package/android/src/main/java/com/fireworksdk/bridge/models/FWShoppingCtaResult.kt +17 -0
- package/android/src/main/java/com/fireworksdk/bridge/models/FWShoppingCtaResultDeserializer.kt +33 -0
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/manager/FWStoryBlockManager.kt +126 -45
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/manager/FWVideoFeedManager.kt +6 -3
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/models/FWVideoShoppingInterface.kt +2 -2
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/module/FWNavigatorModule.kt +9 -1
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/module/FWVideoShoppingModule.kt +81 -50
- package/android/src/main/java/com/fireworksdk/bridge/reactnative/utils/FWEventUtils.kt +9 -2
- package/android/src/main/java/com/fireworksdk/bridge/utils/FWConfigUtil.kt +1 -1
- package/android/src/main/java/com/fireworksdk/bridge/utils/FWGlobalDataUtil.kt +4 -0
- package/android/src/main/java/com/fireworksdk/bridge/utils/FWLanguageUtil.kt +48 -16
- package/android/src/main/res/layout/fw_bridge_story_block.xml +24 -0
- package/ios/Components/StoryBlock.swift +33 -2
- package/ios/Components/StoryBlockManager.m +32 -0
- package/ios/Components/VideoFeed.swift +9 -29
- package/ios/Components/VideoFeedManager.m +11 -6
- package/ios/FireworkSdk.xcodeproj/project.pbxproj +378 -104
- package/ios/Models/NativeToRN/FireworkEventName.swift +3 -1
- package/ios/Models/RNToNative/RCTConvert+Shopping.swift +21 -0
- package/ios/Models/RNToNative/RCTConvert+VideoFeed.swift +27 -0
- package/ios/Modules/FWNavigatorModule/FWNavigatorModule.swift +5 -1
- package/ios/Modules/FireworkSDKModule/FireworkSDKModule.m +1 -0
- package/ios/Modules/FireworkSDKModule/FireworkSDKModule.swift +30 -0
- package/ios/Modules/Shopping/ProductInfoViewConfiguration.swift +13 -0
- package/ios/Modules/Shopping/ShoppingCTAResult.swift +16 -0
- package/ios/Modules/Shopping/ShoppingModule.m +2 -1
- package/ios/Modules/Shopping/ShoppingModule.swift +106 -30
- package/ios/Support/MultiHostStreaming/FWMultiHostStreaming.podspec +24 -0
- package/ios/Support/MultiHostStreaming/src/MultiHostStreamingSDK.swift +17 -0
- package/ios/Utils/AppLanguage/Bundle+FWSwizzle.swift +58 -0
- package/ios/Utils/AppLanguage/FWAppLanguageManager.swift +139 -0
- package/ios/Utils/AppLanguage/FWLanguageUtil.swift +43 -0
- package/ios/Utils/AppLanguage/NumberFormatter+FWSwizzle.swift +25 -0
- package/ios/Utils/AppLanguage/UIImageView+FWSwizzle.swift +91 -0
- package/ios/Utils/AppLanguage/UILabel+FWSwizzle.swift +98 -0
- package/ios/Utils/AppLanguage/UITextField+FWSwizzle.swift +97 -0
- package/ios/Utils/AppLanguage/UITextView+FWSwizzle.swift +97 -0
- package/ios/Utils/AppLanguage/UIView+FWSwizzle.swift +38 -0
- package/ios/Utils/AppLanguage/UIViewController+FWSwizzle.swift +32 -0
- package/ios/Utils/AppLanguage/UIWindow+FWSwizzle.swift +26 -0
- package/ios/Utils/AppLanguage/URLSession+FWSwizzle.swift +69 -0
- package/ios/Utils/{DispatchQueue+FWOnce.swift → Extensions/DispatchQueue+FWOnce.swift} +3 -3
- package/ios/Utils/{UINavigationController+FWSwizzle.swift → Extensions/Swizzle/UINavigationController+FWSwizzle.swift} +6 -8
- package/ios/Utils/Extensions/UIView+FWUIHierarchy.swift +47 -0
- package/ios/Utils/FWRTL/Classes/Manager/FWRTLManager.h +25 -0
- package/ios/Utils/FWRTL/Classes/Manager/FWRTLManager.m +75 -0
- package/ios/Utils/FWRTL/Classes/UICategories/CALayer+FWRTL.h +21 -0
- package/ios/Utils/FWRTL/Classes/UICategories/CALayer+FWRTL.m +124 -0
- package/ios/Utils/FWRTL/Classes/UICategories/FWRTLRemoteViewControllerAdaptor.h +11 -0
- package/ios/Utils/FWRTL/Classes/UICategories/FWRTLRemoteViewControllerAdaptor.m +86 -0
- package/ios/Utils/FWRTL/Classes/UICategories/FWRTLWhiteListManager.h +16 -0
- package/ios/Utils/FWRTL/Classes/UICategories/FWRTLWhiteListManager.m +55 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UILabel+FWRTL.h +18 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UILabel+FWRTL.m +39 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UIView+FWRTL.h +54 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UIView+FWRTL.m +141 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UIWindow+FWRTL.h +16 -0
- package/ios/Utils/FWRTL/Classes/UICategories/UIWindow+FWRTL.m +20 -0
- package/ios/Utils/FWRTL/Classes/Utils/FWRTLDefinitions.h +52 -0
- package/ios/Utils/FWRTL/Classes/Utils/NSObject+FWRTLReloadBlock.h +19 -0
- package/ios/Utils/FWRTL/Classes/Utils/NSObject+FWRTLReloadBlock.m +49 -0
- package/ios/Utils/FWRTL/Classes/Utils/NSString+FWRTL.h +21 -0
- package/ios/Utils/FWRTL/Classes/Utils/NSString+FWRTL.m +38 -0
- package/ios/Utils/FWRTL/Classes/Utils/UIImage+FWRTL.h +18 -0
- package/ios/Utils/FWRTL/Classes/Utils/UIImage+FWRTL.m +43 -0
- package/ios/Utils/FWSwizzleLoader.m +6 -1
- package/ios/Utils/FWSwizzleLoader.swift +13 -0
- package/ios/Utils/FWSwizzleUtil.swift +17 -9
- package/ios/react_native_firework_sdk.h +1 -0
- package/ios/scripts/react_native_firework_sdk_pods.rb +31 -0
- package/lib/commonjs/FireworkSDK.js +21 -4
- package/lib/commonjs/FireworkSDK.js.map +1 -1
- package/lib/commonjs/VideoShopping.js +20 -37
- package/lib/commonjs/VideoShopping.js.map +1 -1
- package/lib/commonjs/components/StoryBlock.js +193 -116
- package/lib/commonjs/components/StoryBlock.js.map +1 -1
- package/lib/commonjs/components/VideoFeed.js +32 -10
- package/lib/commonjs/components/VideoFeed.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/FWEventName.js +2 -0
- package/lib/commonjs/models/FWEventName.js.map +1 -1
- package/lib/commonjs/models/ShoppingCTAResult.js +2 -0
- package/lib/commonjs/modules/FireworkSDKModule.js.map +1 -1
- package/lib/commonjs/modules/ShoppingModule.js.map +1 -1
- package/lib/module/FireworkSDK.js +21 -4
- package/lib/module/FireworkSDK.js.map +1 -1
- package/lib/module/VideoShopping.js +20 -39
- package/lib/module/VideoShopping.js.map +1 -1
- package/lib/module/components/StoryBlock.js +181 -116
- package/lib/module/components/StoryBlock.js.map +1 -1
- package/lib/module/components/VideoFeed.js +37 -10
- package/lib/module/components/VideoFeed.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/FWEventName.js +2 -0
- package/lib/module/models/FWEventName.js.map +1 -1
- package/lib/module/models/ShoppingCTAResult.js +2 -0
- package/lib/module/modules/FireworkSDKModule.js.map +1 -1
- package/lib/module/modules/ShoppingModule.js.map +1 -1
- package/lib/typescript/FireworkSDK.d.ts +7 -4
- package/lib/typescript/VideoShopping.d.ts +9 -11
- package/lib/typescript/components/StoryBlock.d.ts +19 -16
- package/lib/typescript/components/VideoFeed.d.ts +15 -6
- package/lib/typescript/index.d.ts +6 -6
- package/lib/typescript/models/FWEventName.d.ts +2 -0
- package/lib/typescript/models/FWEvents.d.ts +14 -1
- package/lib/typescript/models/ProductInfoViewConfiguration.d.ts +36 -1
- package/lib/typescript/models/ShoppingCTAResult.d.ts +11 -0
- package/lib/typescript/modules/FireworkSDKModule.d.ts +1 -2
- package/lib/typescript/modules/ShoppingModule.d.ts +2 -1
- package/package.json +5 -4
- package/react-native-firework-sdk.podspec +26 -24
- package/src/FireworkSDK.ts +18 -5
- package/src/VideoShopping.ts +40 -53
- package/src/components/StoryBlock.tsx +201 -88
- package/src/components/VideoFeed.tsx +32 -12
- package/src/index.ts +15 -7
- package/src/models/FWEventName.ts +2 -0
- package/src/models/FWEvents.ts +14 -1
- package/src/models/ProductInfoViewConfiguration.ts +38 -1
- package/src/models/ShoppingCTAResult.ts +11 -0
- package/src/modules/FireworkSDKModule.ts +1 -2
- package/src/modules/ShoppingModule.ts +5 -5
- package/android/src/main/java/com/fireworksdk/bridge/constants/FWCommandConstant.kt +0 -6
- package/ios/Utils/UIView+ParentViewController.swift +0 -21
- package/lib/commonjs/models/AddToCartResult.js +0 -2
- package/lib/module/models/AddToCartResult.js +0 -2
- package/lib/typescript/models/AddToCartResult.d.ts +0 -10
- package/src/models/AddToCartResult.ts +0 -10
- /package/ios/Utils/{String+Color.swift → Extensions/String+Color.swift} +0 -0
- /package/ios/Utils/{UIView+Constraints.swift → Extensions/UIView+Constraints.swift} +0 -0
- /package/ios/Utils/{UIViewController+AttachChild.swift → Extensions/UIViewController+AttachChild.swift} +0 -0
- /package/lib/commonjs/models/{AddToCartResult.js.map → ShoppingCTAResult.js.map} +0 -0
- /package/lib/module/models/{AddToCartResult.js.map → ShoppingCTAResult.js.map} +0 -0
package/android/build.gradle
CHANGED
|
@@ -50,8 +50,8 @@ android {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
buildFeatures {
|
|
54
|
+
viewBinding = true
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
lintOptions {
|
|
@@ -151,7 +151,7 @@ dependencies {
|
|
|
151
151
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def fireworkSdkVersion = "6.
|
|
154
|
+
def fireworkSdkVersion = "6.1.1"
|
|
155
155
|
implementation "com.firework:sdk:$fireworkSdkVersion"
|
|
156
156
|
implementation "com.firework.external.imageloading:glide:$fireworkSdkVersion"
|
|
157
157
|
implementation "com.firework.external.livestream:singleHostPlayer:$fireworkSdkVersion"
|
|
@@ -165,6 +165,8 @@ dependencies {
|
|
|
165
165
|
|
|
166
166
|
// required to avoid crash on Android 12 API 31
|
|
167
167
|
implementation 'androidx.work:work-runtime-ktx:2.7.0'
|
|
168
|
+
|
|
169
|
+
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
configurations.all {
|
package/android/src/main/java/com/fireworksdk/bridge/components/videofeed/StoryBlockFragment.kt
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
package com.fireworksdk.bridge.components.videofeed
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.view.LayoutInflater
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.view.ViewGroup
|
|
7
|
+
import androidx.activity.OnBackPressedCallback
|
|
8
|
+
import androidx.fragment.app.Fragment
|
|
9
|
+
import com.firework.storyblock.FeedLoadListener
|
|
10
|
+
import com.firework.storyblock.FwStoryBlockView
|
|
11
|
+
import com.fireworksdk.bridge.databinding.FwBridgeStoryBlockBinding
|
|
12
|
+
import com.fireworksdk.bridge.models.FWVideoFeedPropsModel
|
|
13
|
+
import com.fireworksdk.bridge.utils.FWConfigUtil
|
|
14
|
+
|
|
15
|
+
class StoryBlockFragment : Fragment() {
|
|
16
|
+
|
|
17
|
+
private var _binding: FwBridgeStoryBlockBinding? = null
|
|
18
|
+
private val binding: FwBridgeStoryBlockBinding get() = _binding!!
|
|
19
|
+
|
|
20
|
+
private var feedLoadListener: FeedLoadListener? = null
|
|
21
|
+
private var fullScreenStateChangeListener: FwStoryBlockView.OnFullscreenStateChangeListener? = null
|
|
22
|
+
private var videoFeedPropsModel: FWVideoFeedPropsModel = FWVideoFeedPropsModel()
|
|
23
|
+
|
|
24
|
+
override fun onCreateView(
|
|
25
|
+
inflater: LayoutInflater,
|
|
26
|
+
container: ViewGroup?,
|
|
27
|
+
savedInstanceState: Bundle?,
|
|
28
|
+
): View {
|
|
29
|
+
_binding = FwBridgeStoryBlockBinding.inflate(inflater)
|
|
30
|
+
return binding.root
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
34
|
+
super.onViewCreated(view, savedInstanceState)
|
|
35
|
+
afterUpdateTransaction()
|
|
36
|
+
// need to call it after init()
|
|
37
|
+
binding.storyBlock.setFeedLoadListener(feedLoadListener)
|
|
38
|
+
binding.storyBlock.setOnFullscreenStateChangeListener(fullScreenStateChangeListener)
|
|
39
|
+
// addBackPressedCallback()
|
|
40
|
+
// Handler(Looper.getMainLooper()).postDelayed({
|
|
41
|
+
// addBackPressedCallback()
|
|
42
|
+
// }, 10000)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
override fun onDestroyView() {
|
|
47
|
+
// removeBackPressedCallback()
|
|
48
|
+
_binding = null
|
|
49
|
+
super.onDestroyView()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private fun afterUpdateTransaction() {
|
|
53
|
+
val viewOptionsBuilder = FWConfigUtil.generateViewOptionsBuilder(videoFeedPropsModel)
|
|
54
|
+
binding.storyBlock.init(
|
|
55
|
+
childFragmentManager,
|
|
56
|
+
requireActivity().lifecycle,
|
|
57
|
+
viewOptionsBuilder.build(),
|
|
58
|
+
binding.storyBlockContainer,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fun setProps(props: FWVideoFeedPropsModel?) {
|
|
63
|
+
props ?: return
|
|
64
|
+
videoFeedPropsModel = props
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fun play() {
|
|
68
|
+
binding.storyBlock.play()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fun pause() {
|
|
72
|
+
binding.storyBlock.pause()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fun toggleFullScreen() {
|
|
76
|
+
binding.storyBlock.toggleFullscreen()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fun setFeedLoadListener(feedLoadListener: FeedLoadListener?) {
|
|
80
|
+
this.feedLoadListener = feedLoadListener
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun setOnFullScreenStateChangedListener(listener: FwStoryBlockView.OnFullscreenStateChangeListener?) {
|
|
84
|
+
this.fullScreenStateChangeListener = listener
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fun isFullScreen(): Boolean {
|
|
88
|
+
return binding.storyBlock.isFullscreen()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private val mBackPressedCallback: OnBackPressedCallback by lazy {
|
|
92
|
+
object : OnBackPressedCallback(true) {
|
|
93
|
+
override fun handleOnBackPressed() {
|
|
94
|
+
if (binding.storyBlock.isFullscreen()) {
|
|
95
|
+
binding.storyBlock.toggleFullscreen()
|
|
96
|
+
} else {
|
|
97
|
+
isEnabled = false
|
|
98
|
+
requireActivity().onBackPressed()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private fun addBackPressedCallback() {
|
|
105
|
+
requireActivity()
|
|
106
|
+
.onBackPressedDispatcher
|
|
107
|
+
.addCallback(
|
|
108
|
+
viewLifecycleOwner,
|
|
109
|
+
mBackPressedCallback
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fun removeBackPressedCallback() {
|
|
114
|
+
mBackPressedCallback.remove()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun onPause() {
|
|
118
|
+
super.onPause()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun onResume() {
|
|
122
|
+
super.onResume()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun onDestroy() {
|
|
126
|
+
super.onDestroy()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
package/android/src/main/java/com/fireworksdk/bridge/components/videofeed/StoryBlockFrameLayout.kt
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.fireworksdk.bridge.components.videofeed
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.AttributeSet
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import com.fireworksdk.bridge.models.*
|
|
7
|
+
|
|
8
|
+
class StoryBlockFrameLayout(
|
|
9
|
+
context: Context,
|
|
10
|
+
attrs: AttributeSet?
|
|
11
|
+
) : FrameLayout(context, attrs) {
|
|
12
|
+
|
|
13
|
+
constructor(context: Context) : this(context, null)
|
|
14
|
+
|
|
15
|
+
private var videoFeedPropsModel: FWVideoFeedPropsModel = FWVideoFeedPropsModel()
|
|
16
|
+
private var storyBlockFragment by weakProperty<StoryBlockFragment?>()
|
|
17
|
+
|
|
18
|
+
fun setSource(source: String?) {
|
|
19
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
20
|
+
source = source
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fun setChannel(channel: String?) {
|
|
25
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
26
|
+
channel = channel
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fun setPlaylist(playlist: String?) {
|
|
31
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
32
|
+
playlist = playlist
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun setDynamicContentParameters(parametersMap: HashMap<String, List<String>>?) {
|
|
37
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
38
|
+
dynamicContentParameters = parametersMap
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun setEnablePictureInPicture(enablePictureInPicture: Boolean?) {
|
|
43
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
44
|
+
enablePictureInPicture = enablePictureInPicture
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun setVideoFeedConfig(config: FWVideoFeedConfigModel?) {
|
|
49
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
50
|
+
videoFeedConfiguration = config
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fun setVideoPlayerConfig(config: FWVideoPlayerConfigModel?) {
|
|
55
|
+
videoFeedPropsModel = videoFeedPropsModel.copy(
|
|
56
|
+
videoPlayerConfiguration = config
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fun setFragment(fragment: StoryBlockFragment) {
|
|
61
|
+
this.storyBlockFragment = fragment
|
|
62
|
+
fragment.setProps(videoFeedPropsModel)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun getFragment(): StoryBlockFragment? {
|
|
66
|
+
return storyBlockFragment
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun play() {
|
|
70
|
+
storyBlockFragment?.play()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun pause() {
|
|
74
|
+
storyBlockFragment?.pause()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fun toggleFullScreen() {
|
|
78
|
+
storyBlockFragment?.toggleFullScreen()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fun destroy() {
|
|
82
|
+
storyBlockFragment = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -31,7 +31,7 @@ enum class FWVideoPlaybackSubEventName(val rawValue: String) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
enum class FWVideoShoppingEventName(val rawValue: String) {
|
|
34
|
-
|
|
34
|
+
CtaButtonClick("fw:shopping:cta-button-click"),
|
|
35
35
|
ClickCartIcon("fw:shopping:click-cart-icon"),
|
|
36
36
|
UpdateProductDetails("fw:shopping:update-product-details"),
|
|
37
37
|
WillDisplayProduct("fw:shopping:will-display-product"),
|
|
@@ -47,4 +47,5 @@ enum class FWLiveStreamEventName(val rawValue: String) {
|
|
|
47
47
|
|
|
48
48
|
enum class FWStoryBlockEventName(val rawValue: String) {
|
|
49
49
|
StoryBlockLoadFinished("onStoryBlockLoadFinished"),
|
|
50
|
+
StoryBlockFullScreenStateChanged("onStoryBlockFullScreenStateChanged"),
|
|
50
51
|
}
|
package/android/src/main/java/com/fireworksdk/bridge/models/FWProductInfoViewConfiguration.kt
CHANGED
|
@@ -5,13 +5,26 @@ import kotlinx.android.parcel.Parcelize
|
|
|
5
5
|
|
|
6
6
|
@Parcelize
|
|
7
7
|
data class FWProductInfoViewConfiguration(
|
|
8
|
-
|
|
8
|
+
val ctaButton: CtaButtonConfiguration? = null,
|
|
9
|
+
val linkButton: LinkButtonConfiguration? = null,
|
|
9
10
|
) : Parcelable {
|
|
10
11
|
|
|
11
12
|
@Parcelize
|
|
12
|
-
data class
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
data class CtaButtonConfiguration(
|
|
14
|
+
val text: TextValue? = null,
|
|
15
|
+
val backgroundColor: String? = null,
|
|
16
|
+
val textColor: String? = null,
|
|
17
|
+
val fontSize: Int? = null,
|
|
18
|
+
) : Parcelable {
|
|
19
|
+
|
|
20
|
+
enum class TextValue(val rawValue: String) {
|
|
21
|
+
AddToCart("addToCart"),
|
|
22
|
+
ShopNow("shopNow"),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Parcelize
|
|
27
|
+
data class LinkButtonConfiguration(
|
|
28
|
+
val isHidden: Boolean? = null,
|
|
16
29
|
) : Parcelable
|
|
17
30
|
}
|
|
@@ -4,32 +4,56 @@ import org.json.JSONObject
|
|
|
4
4
|
|
|
5
5
|
object FWProductInfoViewConfigurationDeserializer {
|
|
6
6
|
|
|
7
|
-
private const val
|
|
7
|
+
private const val CTA_BUTTON = "ctaButton"
|
|
8
|
+
private const val LINK_BUTTON = "linkButton"
|
|
8
9
|
|
|
10
|
+
private const val TEXT_KEY = "text"
|
|
9
11
|
private const val BACKGROUND_COLOR_KEY = "backgroundColor"
|
|
10
12
|
private const val TEXT_COLOR_KEY = "textColor"
|
|
11
13
|
private const val FONT_SIZE_KEY = "fontSize"
|
|
12
14
|
|
|
15
|
+
private const val IS_HIDDEN_KEY = "isHidden"
|
|
16
|
+
|
|
13
17
|
fun deserialize(responseJson: JSONObject?): FWProductInfoViewConfiguration? {
|
|
14
18
|
responseJson ?: return null
|
|
15
19
|
|
|
16
|
-
val
|
|
20
|
+
val ctaButtonConfiguration = deserializeCtaButtonConfiguration(responseJson.optJSONObject(CTA_BUTTON))
|
|
21
|
+
val linkButtonConfiguration = deserializeLinkButtonConfiguration(responseJson.optJSONObject(LINK_BUTTON))
|
|
17
22
|
|
|
18
23
|
return FWProductInfoViewConfiguration(
|
|
19
|
-
|
|
24
|
+
ctaButton = ctaButtonConfiguration,
|
|
25
|
+
linkButton = linkButtonConfiguration,
|
|
20
26
|
)
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
private fun
|
|
24
|
-
val
|
|
25
|
-
val
|
|
26
|
-
val
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
private fun deserializeCtaButtonConfiguration(ctaButtonConfiguration: JSONObject?): FWProductInfoViewConfiguration.CtaButtonConfiguration {
|
|
30
|
+
val textString = ctaButtonConfiguration?.optString(TEXT_KEY)
|
|
31
|
+
val backgroundColor = ctaButtonConfiguration?.optString(BACKGROUND_COLOR_KEY)
|
|
32
|
+
val textColor = ctaButtonConfiguration?.optString(TEXT_COLOR_KEY)
|
|
33
|
+
val fontSize = ctaButtonConfiguration?.optInt(FONT_SIZE_KEY)
|
|
34
|
+
|
|
35
|
+
var text = FWProductInfoViewConfiguration.CtaButtonConfiguration.TextValue.AddToCart
|
|
36
|
+
when (textString) {
|
|
37
|
+
FWProductInfoViewConfiguration.CtaButtonConfiguration.TextValue.ShopNow.rawValue -> {
|
|
38
|
+
text = FWProductInfoViewConfiguration.CtaButtonConfiguration.TextValue.ShopNow
|
|
39
|
+
}
|
|
40
|
+
else -> {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return FWProductInfoViewConfiguration.CtaButtonConfiguration(
|
|
44
|
+
text = text,
|
|
29
45
|
backgroundColor = backgroundColor,
|
|
30
46
|
textColor = textColor,
|
|
31
47
|
fontSize = fontSize,
|
|
32
48
|
)
|
|
33
49
|
}
|
|
34
50
|
|
|
51
|
+
private fun deserializeLinkButtonConfiguration(addToCartButtonStyleJson: JSONObject?): FWProductInfoViewConfiguration.LinkButtonConfiguration {
|
|
52
|
+
val isHidden = addToCartButtonStyleJson?.optBoolean(IS_HIDDEN_KEY)
|
|
53
|
+
|
|
54
|
+
return FWProductInfoViewConfiguration.LinkButtonConfiguration(
|
|
55
|
+
isHidden = isHidden,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.fireworksdk.bridge.models
|
|
2
|
+
|
|
3
|
+
import android.os.Parcelable
|
|
4
|
+
import kotlinx.android.parcel.Parcelize
|
|
5
|
+
|
|
6
|
+
@Parcelize
|
|
7
|
+
data class FWShoppingCtaResult(
|
|
8
|
+
val res: Res? = null,
|
|
9
|
+
val tips: String? = null,
|
|
10
|
+
) : Parcelable {
|
|
11
|
+
|
|
12
|
+
enum class Res(val rawValue: String) {
|
|
13
|
+
Success("success"),
|
|
14
|
+
Fail("fail"),
|
|
15
|
+
Loading("loading"),
|
|
16
|
+
}
|
|
17
|
+
}
|
package/android/src/main/java/com/fireworksdk/bridge/models/FWShoppingCtaResultDeserializer.kt
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.fireworksdk.bridge.models
|
|
2
|
+
|
|
3
|
+
import org.json.JSONObject
|
|
4
|
+
|
|
5
|
+
object FWShoppingCtaResultDeserializer {
|
|
6
|
+
|
|
7
|
+
private const val RES_KEY = "res"
|
|
8
|
+
private const val TIPS_KEY = "tips"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
fun deserialize(responseJson: JSONObject?): FWShoppingCtaResult? {
|
|
12
|
+
responseJson?: return null
|
|
13
|
+
|
|
14
|
+
val resString = responseJson.optString(RES_KEY)
|
|
15
|
+
val tips = responseJson.optString(TIPS_KEY)
|
|
16
|
+
|
|
17
|
+
var res = FWShoppingCtaResult.Res.Success
|
|
18
|
+
when (resString) {
|
|
19
|
+
FWShoppingCtaResult.Res.Fail.rawValue -> {
|
|
20
|
+
res = FWShoppingCtaResult.Res.Fail
|
|
21
|
+
}
|
|
22
|
+
FWShoppingCtaResult.Res.Loading.rawValue -> {
|
|
23
|
+
res = FWShoppingCtaResult.Res.Loading
|
|
24
|
+
}
|
|
25
|
+
else -> {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return FWShoppingCtaResult(
|
|
29
|
+
res = res,
|
|
30
|
+
tips = tips,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
package/android/src/main/java/com/fireworksdk/bridge/reactnative/manager/FWStoryBlockManager.kt
CHANGED
|
@@ -3,10 +3,7 @@ package com.fireworksdk.bridge.reactnative.manager
|
|
|
3
3
|
import android.view.Choreographer
|
|
4
4
|
import android.view.View
|
|
5
5
|
import android.view.ViewGroup
|
|
6
|
-
import android.widget.FrameLayout
|
|
7
|
-
import androidx.annotation.Nullable
|
|
8
6
|
import androidx.appcompat.app.AppCompatActivity
|
|
9
|
-
import com.facebook.react.bridge.Dynamic
|
|
10
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
8
|
import com.facebook.react.bridge.ReadableArray
|
|
12
9
|
import com.facebook.react.bridge.ReadableMap
|
|
@@ -14,78 +11,102 @@ import com.facebook.react.common.MapBuilder
|
|
|
14
11
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
15
12
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
16
13
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
17
|
-
import com.
|
|
18
|
-
import com.fireworksdk.bridge.
|
|
19
|
-
import com.fireworksdk.bridge.
|
|
14
|
+
import com.firework.storyblock.FeedLoadState
|
|
15
|
+
import com.fireworksdk.bridge.components.videofeed.StoryBlockFragment
|
|
16
|
+
import com.fireworksdk.bridge.components.videofeed.StoryBlockFrameLayout
|
|
17
|
+
import com.fireworksdk.bridge.models.*
|
|
18
|
+
import com.fireworksdk.bridge.reactnative.utils.FWEventUtils
|
|
19
|
+
import com.fireworksdk.bridge.utils.FWGlobalDataUtil
|
|
20
20
|
import com.fireworksdk.bridge.utils.FWLogUtils
|
|
21
|
+
import org.json.JSONObject
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class FWStoryBlockManager(
|
|
24
25
|
private val reactContext: ReactApplicationContext
|
|
25
|
-
) : ViewGroupManager<
|
|
26
|
-
|
|
27
|
-
private val videoFeedPropsModel: FWVideoFeedPropsModel by lazy { FWVideoFeedPropsModel() }
|
|
28
|
-
|
|
29
|
-
private var viewId: Int? = null
|
|
26
|
+
) : ViewGroupManager<StoryBlockFrameLayout>() {
|
|
30
27
|
|
|
31
28
|
override fun getName(): String {
|
|
32
29
|
return REACT_CLASS
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
override fun createViewInstance(reactContext: ThemedReactContext):
|
|
36
|
-
return
|
|
32
|
+
override fun createViewInstance(reactContext: ThemedReactContext): StoryBlockFrameLayout {
|
|
33
|
+
return StoryBlockFrameLayout(reactContext)
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
override fun onAfterUpdateTransaction(view:
|
|
36
|
+
override fun onAfterUpdateTransaction(view: StoryBlockFrameLayout) {
|
|
40
37
|
super.onAfterUpdateTransaction(view)
|
|
38
|
+
// after createFragment
|
|
41
39
|
FWLogUtils.d { "FWStoryBlockManager onAfterUpdateTransaction" }
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
override fun getCommandsMap(): Map<String, Int> {
|
|
43
|
+
val map: MutableMap<String, Int> = HashMap()
|
|
44
|
+
map["create"] = COMMAND_CREATE
|
|
45
|
+
map["play"] = COMMAND_PLAY
|
|
46
|
+
map["pause"] = COMMAND_PAUSE
|
|
47
|
+
map["toggle_full_screen"] = COMMAND_TOGGLE_FULL_SCREEN
|
|
48
|
+
return map
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
override fun receiveCommand(
|
|
50
|
-
root:
|
|
52
|
+
root: StoryBlockFrameLayout,
|
|
51
53
|
commandId: String?,
|
|
52
54
|
args: ReadableArray?
|
|
53
55
|
) {
|
|
54
56
|
super.receiveCommand(root, commandId, args)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
FWLogUtils.d { "FWStoryBlockManager receiveCommand commandId: $commandId, reactNativeViewId: $reactNativeViewId" }
|
|
57
|
+
FWLogUtils.d { "FWStoryBlockManager receiveCommand commandId: $commandId, root.isAttachedToWindow: ${root.isAttachedToWindow}" }
|
|
58
58
|
when (commandId?.toInt()) {
|
|
59
|
-
COMMAND_CREATE ->
|
|
59
|
+
COMMAND_CREATE -> {
|
|
60
|
+
val reactNativeViewId = requireNotNull(args).getInt(0)
|
|
61
|
+
createFragment(root, reactNativeViewId)
|
|
62
|
+
}
|
|
63
|
+
COMMAND_PLAY -> root.play()
|
|
64
|
+
COMMAND_PAUSE -> root.pause()
|
|
65
|
+
COMMAND_TOGGLE_FULL_SCREEN -> root.toggleFullScreen()
|
|
60
66
|
else -> {}
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
@ReactProp(name = "source")
|
|
65
|
-
fun setSource(view:
|
|
66
|
-
|
|
71
|
+
fun setSource(view: StoryBlockFrameLayout, source: String?) {
|
|
72
|
+
view.setSource(source)
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
@ReactProp(name = "channel")
|
|
70
|
-
fun setChannel(view:
|
|
71
|
-
|
|
76
|
+
fun setChannel(view: StoryBlockFrameLayout, channel: String?) {
|
|
77
|
+
view.setChannel(channel)
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
@ReactProp(name = "playlist")
|
|
75
|
-
fun setPlaylist(view:
|
|
76
|
-
|
|
81
|
+
fun setPlaylist(view: StoryBlockFrameLayout, playlist: String?) {
|
|
82
|
+
view.setPlaylist(playlist)
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
@ReactProp(name = "dynamicContentParameters")
|
|
80
|
-
fun setDynamicContentParameters(view:
|
|
86
|
+
fun setDynamicContentParameters(view: StoryBlockFrameLayout, parameters: ReadableMap?) {
|
|
81
87
|
val parametersMap = parameters?.toHashMap() as? HashMap<String, List<String>>
|
|
82
|
-
|
|
88
|
+
view.setDynamicContentParameters(parametersMap)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@ReactProp(name = "enablePictureInPicture")
|
|
92
|
+
fun setEnablePictureInPicture(view: StoryBlockFrameLayout, enablePictureInPicture: Boolean?) {
|
|
93
|
+
view.setEnablePictureInPicture(enablePictureInPicture)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@ReactProp(name = "videoFeedConfiguration")
|
|
97
|
+
fun setVideoFeedConfig(view: StoryBlockFrameLayout, config: ReadableMap?) {
|
|
98
|
+
val configMap = config?.toHashMap() ?: hashMapOf()
|
|
99
|
+
val jsonObject = JSONObject(configMap)
|
|
100
|
+
val videoFeedConfigModel = FWVideoFeedConfigModelDeserializer.deserialize(jsonObject)
|
|
101
|
+
view.setVideoFeedConfig(videoFeedConfigModel)
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
@
|
|
86
|
-
fun
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
@ReactProp(name = "videoPlayerConfiguration")
|
|
105
|
+
fun setVideoPlayerConfig(view: StoryBlockFrameLayout, config: ReadableMap?) {
|
|
106
|
+
val configMap = config?.toHashMap() ?: hashMapOf()
|
|
107
|
+
val jsonObject = JSONObject(configMap)
|
|
108
|
+
val videoPlayerConfigModel = FWVideoPlayerConfigModelDeserializer.deserialize(jsonObject)
|
|
109
|
+
view.setVideoPlayerConfig(videoPlayerConfigModel)
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
private fun setupLayout(view: View) {
|
|
@@ -100,46 +121,106 @@ class FWStoryBlockManager(
|
|
|
100
121
|
})
|
|
101
122
|
}
|
|
102
123
|
|
|
103
|
-
/**
|
|
104
|
-
* Layout all children properly
|
|
105
|
-
*/
|
|
106
124
|
private fun manuallyLayoutChildren(view: View) {
|
|
107
125
|
view.measure(
|
|
108
126
|
View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY),
|
|
109
127
|
View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY)
|
|
110
128
|
)
|
|
111
|
-
|
|
112
129
|
view.layout(view.left, view.top, view.right, view.bottom)
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
private fun addStoryBlockListener(fragment: StoryBlockFragment, reactNativeViewId: Int) {
|
|
133
|
+
fragment.setFeedLoadListener { feedLoadState ->
|
|
134
|
+
when (feedLoadState) {
|
|
135
|
+
FeedLoadState.FeedLoaded -> {
|
|
136
|
+
FWEventUtils.receiveStoryBlockLoadFinishedSuccessEvent(reactContext, reactNativeViewId)
|
|
137
|
+
}
|
|
138
|
+
FeedLoadState.FeedLoadFailed -> {
|
|
139
|
+
FWEventUtils.receiveStoryBlockLoadFinishedFailedEvent(
|
|
140
|
+
reactContext,
|
|
141
|
+
reactNativeViewId,
|
|
142
|
+
"FeedLoadFailed",
|
|
143
|
+
"FeedLoadFailed"
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
else -> {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
115
149
|
|
|
116
|
-
|
|
150
|
+
fragment.setOnFullScreenStateChangedListener { isFullScreen ->
|
|
151
|
+
if (isFullScreen) {
|
|
152
|
+
FWGlobalDataUtil.storyBlockFragment = fragment
|
|
153
|
+
}
|
|
154
|
+
FWEventUtils.receiveStoryBlockFullScreenStateChangedEvent(
|
|
155
|
+
reactContext,
|
|
156
|
+
reactNativeViewId,
|
|
157
|
+
isFullScreen
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
private fun createFragment(root: StoryBlockFrameLayout, reactNativeViewId: Int?) {
|
|
117
164
|
FWLogUtils.d { "FWStoryBlockManager createFragment, reactNativeViewId = $reactNativeViewId" }
|
|
118
165
|
reactNativeViewId ?: return
|
|
119
166
|
val activity = (reactContext.currentActivity as AppCompatActivity?) ?: return
|
|
120
167
|
|
|
121
|
-
viewId = reactNativeViewId
|
|
122
|
-
|
|
123
168
|
val parentView = root.findViewById(reactNativeViewId) as ViewGroup
|
|
124
169
|
// parentView.setBackgroundColor(Color.YELLOW)
|
|
125
170
|
setupLayout(parentView)
|
|
171
|
+
|
|
172
|
+
val fragment = StoryBlockFragment()
|
|
173
|
+
root.setFragment(fragment)
|
|
174
|
+
addStoryBlockListener(fragment, reactNativeViewId)
|
|
175
|
+
|
|
176
|
+
if (parentView.isAttachedToWindow) {
|
|
177
|
+
if (!fragment.isAdded) {
|
|
178
|
+
activity.supportFragmentManager
|
|
179
|
+
.beginTransaction()
|
|
180
|
+
.replace(reactNativeViewId, fragment, reactNativeViewId.toString())
|
|
181
|
+
.commit()
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
parentView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
|
185
|
+
override fun onViewAttachedToWindow(v: View?) {
|
|
186
|
+
FWLogUtils.d { "FWStoryBlockManager createFragment doOnAttach" }
|
|
187
|
+
if (!fragment.isAdded) {
|
|
188
|
+
activity.supportFragmentManager
|
|
189
|
+
.beginTransaction()
|
|
190
|
+
.replace(reactNativeViewId, fragment, reactNativeViewId.toString())
|
|
191
|
+
.commit()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override fun onViewDetachedFromWindow(v: View?) {
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
}
|
|
126
199
|
}
|
|
127
200
|
|
|
128
|
-
override fun onDropViewInstance(view:
|
|
201
|
+
override fun onDropViewInstance(view: StoryBlockFrameLayout) {
|
|
202
|
+
val activity = reactContext.currentActivity as AppCompatActivity
|
|
203
|
+
val fragment = view.getFragment()
|
|
204
|
+
fragment?.let {
|
|
205
|
+
activity.supportFragmentManager.beginTransaction().remove(it).commit()
|
|
206
|
+
}
|
|
207
|
+
view.destroy()
|
|
129
208
|
super.onDropViewInstance(view)
|
|
130
209
|
}
|
|
131
210
|
|
|
132
211
|
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any>? {
|
|
133
212
|
return MapBuilder.builder<String, Any>()
|
|
134
213
|
.put(FWStoryBlockEventName.StoryBlockLoadFinished.rawValue, MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", FWStoryBlockEventName.StoryBlockLoadFinished.rawValue)))
|
|
214
|
+
.put(FWStoryBlockEventName.StoryBlockFullScreenStateChanged.rawValue, MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", FWStoryBlockEventName.StoryBlockFullScreenStateChanged.rawValue)))
|
|
135
215
|
.build()
|
|
136
216
|
}
|
|
137
217
|
|
|
138
218
|
companion object {
|
|
139
219
|
private const val REACT_CLASS = "FWStoryBlock"
|
|
140
|
-
const val
|
|
141
|
-
const val
|
|
142
|
-
private const val
|
|
220
|
+
private const val COMMAND_CREATE = 1000000
|
|
221
|
+
private const val COMMAND_PLAY = 1000001
|
|
222
|
+
private const val COMMAND_PAUSE = 1000002
|
|
223
|
+
private const val COMMAND_TOGGLE_FULL_SCREEN = 1000003
|
|
143
224
|
}
|
|
144
225
|
|
|
145
226
|
}
|