From 4547b9411ecd7d7ec3cfa6d2fa26cf70981438b6 Mon Sep 17 00:00:00 2001
From: Zayar <zayariosdev@icloud.com>
Date: Fri, 22 Nov 2024 19:22:01 +0700
Subject: [PATCH] Update AmityUIKit v4.0.0-beta29

---
 .../AmityUIKit.xcodeproj/project.pbxproj      |   8 +-
 .../AmityUIKit4.xcodeproj/project.pbxproj     |  22 +-
 .../Core/Components/TargetSelectionView.swift |  30 +-
 .../Extenral/Pager/SwiftUIPager/Pager.swift   |   4 +-
 .../Pager/SwiftUIPager/PagerContent.swift     |   7 +-
 .../Core/Extensions/String+Extension.swift    |   4 +-
 .../SocialHome/AmityPostMenuComponent.swift   |   2 +
 .../CommunityProfileViewModel.swift           |  11 +-
 .../PostDetail/AmityPostDetailPage.swift      |   2 +-
 .../StoryTab/AmityStoryTabComponent.swift     |  17 +-
 .../Story/Models/AmityStoryTargetModel.swift  |  14 +-
 .../Pages/ViewStory/AmityViewStoryPage.swift  | 436 +++---------------
 .../AmityViewStoryPageViewModel.swift         | 109 +++++
 .../ChildViews/ProgressBarView.swift          |  14 +-
 .../ViewStory/ChildViews/StoryAdView.swift    |  10 +-
 .../ViewStory/ChildViews/StoryCoreView.swift  | 415 +++++++++--------
 .../ChildViews/StoryCoreViewModel.swift       | 190 ++++++++
 .../ViewStory/ChildViews/StoryImageView.swift |  51 ++
 .../ViewStory/ChildViews/StoryVideoView.swift |  47 ++
 .../project.pbxproj                           |   8 +-
 .../SampleApp.xcodeproj/project.pbxproj       |  18 +-
 .../contents.xcworkspacedata                  |   4 -
 UpstraUIKit/SharedFrameworks/Package.swift    |  20 +-
 .../Components/AmityExpandableLabel.swift     |   8 +-
 24 files changed, 821 insertions(+), 630 deletions(-)
 create mode 100644 UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPageViewModel.swift
 create mode 100644 UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreViewModel.swift
 create mode 100644 UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryImageView.swift
 create mode 100644 UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryVideoView.swift
 delete mode 100644 UpstraUIKit/SharedFrameworks/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

diff --git a/UpstraUIKit/AmityUIKit.xcodeproj/project.pbxproj b/UpstraUIKit/AmityUIKit.xcodeproj/project.pbxproj
index b690a85..855c71e 100644
--- a/UpstraUIKit/AmityUIKit.xcodeproj/project.pbxproj
+++ b/UpstraUIKit/AmityUIKit.xcodeproj/project.pbxproj
@@ -79,6 +79,7 @@
 		1AF0D2CA251371780083D12C /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF0D2C9251371780083D12C /* Log.swift */; };
 		68251A632ADEA16200395696 /* AmityPreviewLinkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68251A612ADEA16200395696 /* AmityPreviewLinkCell.swift */; };
 		68251A642ADEA16200395696 /* AmityPreviewLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 68251A622ADEA16200395696 /* AmityPreviewLinkCell.xib */; };
+		682E02182CF0AE4D00FE1042 /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = 682E02172CF0AE4D00FE1042 /* SharedFrameworks */; };
 		6860B01B2ADE3D650042ED45 /* AmityPreviewLinkWizard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6860B01A2ADE3D650042ED45 /* AmityPreviewLinkWizard.swift */; };
 		686E488F2B19A44900591E2D /* AmityStoryTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686E488D2B19A44900591E2D /* AmityStoryTabViewController.swift */; };
 		686E48902B19A44900591E2D /* AmityStoryTabViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 686E488E2B19A44900591E2D /* AmityStoryTabViewController.xib */; };
@@ -659,7 +660,6 @@
 		D4D7683E26006D7000AD4367 /* AmitySettingContentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D7683D26006D7000AD4367 /* AmitySettingContentIcon.swift */; };
 		D4EC11E525B93DCE0095F507 /* AmityPostSharingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EC11E425B93DCE0095F507 /* AmityPostSharingSettings.swift */; };
 		D4EC11EA25B93E000095F507 /* AmityPostSharingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EC11E925B93E000095F507 /* AmityPostSharingTarget.swift */; };
-		ED5232192CDE38FB00ABA50D /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = ED5232182CDE38FB00ABA50D /* SharedFrameworks */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1340,7 +1340,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				ED5232192CDE38FB00ABA50D /* SharedFrameworks in Frameworks */,
+				682E02182CF0AE4D00FE1042 /* SharedFrameworks in Frameworks */,
 				68F5D9FA2B481E4000A9FA0D /* AmityUIKit4.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -4507,7 +4507,7 @@
 			);
 			name = AmityUIKit;
 			packageProductDependencies = (
-				ED5232182CDE38FB00ABA50D /* SharedFrameworks */,
+				682E02172CF0AE4D00FE1042 /* SharedFrameworks */,
 			);
 			productName = UpstraUIKit;
 			productReference = 72A3503024EA811500DA9D46 /* AmityUIKit.framework */;
@@ -5523,7 +5523,7 @@
 /* End XCConfigurationList section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		ED5232182CDE38FB00ABA50D /* SharedFrameworks */ = {
+		682E02172CF0AE4D00FE1042 /* SharedFrameworks */ = {
 			isa = XCSwiftPackageProductDependency;
 			productName = SharedFrameworks;
 		};
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4.xcodeproj/project.pbxproj b/UpstraUIKit/AmityUIKit4/AmityUIKit4.xcodeproj/project.pbxproj
index 4e13c11..88d4359 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4.xcodeproj/project.pbxproj
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4.xcodeproj/project.pbxproj
@@ -11,6 +11,11 @@
 		68124D9F2B1748DE009B5B4C /* AmityProgressBarElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68124D9E2B1748DE009B5B4C /* AmityProgressBarElement.swift */; };
 		682C76172B3208CC00018F80 /* StoryPermissionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682C76162B3208CC00018F80 /* StoryPermissionChecker.swift */; };
 		682C761B2B3302AB00018F80 /* AmityUIKitConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 682C761A2B3302AB00018F80 /* AmityUIKitConfig.json */; };
+		682E020E2CEE6A7900FE1042 /* AmityViewStoryPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E020D2CEE6A7900FE1042 /* AmityViewStoryPageViewModel.swift */; };
+		682E02122CEE6A8200FE1042 /* StoryCoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E020F2CEE6A8200FE1042 /* StoryCoreViewModel.swift */; };
+		682E02132CEE6A8200FE1042 /* StoryImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E02102CEE6A8200FE1042 /* StoryImageView.swift */; };
+		682E02142CEE6A8200FE1042 /* StoryVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E02112CEE6A8200FE1042 /* StoryVideoView.swift */; };
+		682E02162CF0AE3D00FE1042 /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = 682E02152CF0AE3D00FE1042 /* SharedFrameworks */; };
 		683B9D642C995AFF005619FE /* AmityUserProfileHeaderComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683B9D632C995AFF005619FE /* AmityUserProfileHeaderComponent.swift */; };
 		683DF10A2C6B1CAD005BF06C /* AmityCommunityAddUserPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683DF1092C6B1CAD005BF06C /* AmityCommunityAddUserPage.swift */; };
 		683DF10C2C6B27A4005BF06C /* AmityUserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683DF10B2C6B27A4005BF06C /* AmityUserModel.swift */; };
@@ -494,7 +499,6 @@
 		ED2D02712CB6354C00BF94FA /* PostContentPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D02702CB6354C00BF94FA /* PostContentPollView.swift */; };
 		ED4FA84A2BA01123005D6871 /* AmityLiveChatHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4FA8492BA01123005D6871 /* AmityLiveChatHeaderViewModel.swift */; };
 		ED4FA84C2BA01169005D6871 /* ChannelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4FA84B2BA01169005D6871 /* ChannelManager.swift */; };
-		ED5232172CDE38EF00ABA50D /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = ED5232162CDE38EF00ABA50D /* SharedFrameworks */; };
 		ED6EFA112CB39DB6003A176F /* AmityPollPostComposerPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6EFA102CB39DB6003A176F /* AmityPollPostComposerPage.swift */; };
 		ED6EFA142CB3D3D2003A176F /* PollAnswerResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6EFA132CB3D3D2003A176F /* PollAnswerResultView.swift */; };
 		ED6EFA612CB4FE0F003A176F /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6EFA602CB4FE0F003A176F /* PollOptionView.swift */; };
@@ -519,6 +523,10 @@
 		68124D9E2B1748DE009B5B4C /* AmityProgressBarElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmityProgressBarElement.swift; sourceTree = "<group>"; };
 		682C76162B3208CC00018F80 /* StoryPermissionChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryPermissionChecker.swift; sourceTree = "<group>"; };
 		682C761A2B3302AB00018F80 /* AmityUIKitConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AmityUIKitConfig.json; sourceTree = "<group>"; };
+		682E020D2CEE6A7900FE1042 /* AmityViewStoryPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmityViewStoryPageViewModel.swift; sourceTree = "<group>"; };
+		682E020F2CEE6A8200FE1042 /* StoryCoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryCoreViewModel.swift; sourceTree = "<group>"; };
+		682E02102CEE6A8200FE1042 /* StoryImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryImageView.swift; sourceTree = "<group>"; };
+		682E02112CEE6A8200FE1042 /* StoryVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryVideoView.swift; sourceTree = "<group>"; };
 		683B9D632C995AFF005619FE /* AmityUserProfileHeaderComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmityUserProfileHeaderComponent.swift; sourceTree = "<group>"; };
 		683DF1092C6B1CAD005BF06C /* AmityCommunityAddUserPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmityCommunityAddUserPage.swift; sourceTree = "<group>"; };
 		683DF10B2C6B27A4005BF06C /* AmityUserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmityUserModel.swift; sourceTree = "<group>"; };
@@ -1034,7 +1042,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				ED5232172CDE38EF00ABA50D /* SharedFrameworks in Frameworks */,
+				682E02162CF0AE3D00FE1042 /* SharedFrameworks in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1949,6 +1957,7 @@
 		68D11A312B2239440046D0FB /* ViewStory */ = {
 			isa = PBXGroup;
 			children = (
+				682E020D2CEE6A7900FE1042 /* AmityViewStoryPageViewModel.swift */,
 				68CC6A642B1733D10052B77E /* AmityViewStoryPage.swift */,
 				68D11A322B22399A0046D0FB /* ChildViews */,
 			);
@@ -1958,6 +1967,9 @@
 		68D11A322B22399A0046D0FB /* ChildViews */ = {
 			isa = PBXGroup;
 			children = (
+				682E020F2CEE6A8200FE1042 /* StoryCoreViewModel.swift */,
+				682E02102CEE6A8200FE1042 /* StoryImageView.swift */,
+				682E02112CEE6A8200FE1042 /* StoryVideoView.swift */,
 				68D11A332B2239C50046D0FB /* StoryCoreView.swift */,
 				68D11A352B223A000046D0FB /* ProgressBarView.swift */,
 				68D9BCC82C32FC2B0082685B /* StoryAdView.swift */,
@@ -2889,9 +2901,13 @@
 				6840981F2B313A6F00697E1B /* VideoCacheManager.swift in Sources */,
 				685F16CA2BFCAD060016685F /* MessageReactionConfiguration.swift in Sources */,
 				684AE10C2B0C5CFE00FD7270 /* AmityUIKit4.swift in Sources */,
+				682E020E2CEE6A7900FE1042 /* AmityViewStoryPageViewModel.swift in Sources */,
 				6877D37B2C75DA2F008B3598 /* AmityCommunityMembershipPage.swift in Sources */,
 				6861984C2C116AF900BA81BE /* AmityPostComposerPage.swift in Sources */,
 				686830332C993D52009B1694 /* AmityUserProfilePage.swift in Sources */,
+				682E02122CEE6A8200FE1042 /* StoryCoreViewModel.swift in Sources */,
+				682E02132CEE6A8200FE1042 /* StoryImageView.swift in Sources */,
+				682E02142CEE6A8200FE1042 /* StoryVideoView.swift in Sources */,
 				689EE6A22BEDCB4E00927D51 /* PostManager.swift in Sources */,
 				68EE70C42C1AD40B005A7002 /* AmityMediaAttachmentViewModel.swift in Sources */,
 				A9CEB8212C7F19A10062823A /* AmityTrendingCommunitiesComponent.swift in Sources */,
@@ -3646,7 +3662,7 @@
 /* End XCConfigurationList section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		ED5232162CDE38EF00ABA50D /* SharedFrameworks */ = {
+		682E02152CF0AE3D00FE1042 /* SharedFrameworks */ = {
 			isa = XCSwiftPackageProductDependency;
 			productName = SharedFrameworks;
 		};
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Components/TargetSelectionView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Components/TargetSelectionView.swift
index 85b6b38..4665e1a 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Components/TargetSelectionView.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Components/TargetSelectionView.swift
@@ -101,19 +101,29 @@ class TargetSelectionViewModel: ObservableObject {
                     communities.map { community -> AnyPublisher<AmityCommunityModel?, Never> in
                         let communityModel = AmityCommunityModel(object: community)
                         
-                        if community.onlyAdminCanPost {
+                        // Story permission specifically need to check without considering onlyAdminCanPost
+                        if contentType == .story {
                             return Future<AmityCommunityModel?, Never> { promise in
-                                let permission: AmityPermission
                                 
-                                switch contentType {
-                                case .post, .poll:
-                                    permission = .createPrivilegedPost
-
-                                case .story:
-                                    permission = .manageStoryCommunity
+                                AmityUIKit4Manager.client.hasPermission(.manageStoryCommunity, forCommunity: community.communityId) { success in
+                                    let hasPermission = success
+                                    let allowAllUserCreation = AmityUIKitManagerInternal.shared.client.getSocialSettings()?.story?.allowAllUserToCreateStory ?? false
+                                    let hasStoryManagePermission = (allowAllUserCreation || hasPermission) && communityModel.isJoined
+                                    
+                                    if hasStoryManagePermission {
+                                        promise(.success(communityModel))
+                                    } else {
+                                        promise(.success(nil))
+                                    }
                                 }
-                                
-                                AmityUIKit4Manager.client.hasPermission(permission, forCommunity: community.communityId) { success in
+                            }
+                            .eraseToAnyPublisher()
+                        }
+                        
+                        // Check for post permission
+                        if community.onlyAdminCanPost {
+                            return Future<AmityCommunityModel?, Never> { promise in
+                                AmityUIKit4Manager.client.hasPermission(.createPrivilegedPost, forCommunity: community.communityId) { success in
                                     if success {
                                         promise(.success(communityModel))
                                     } else {
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/Pager.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/Pager.swift
index ccb097e..5038a7d 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/Pager.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/Pager.swift
@@ -45,10 +45,10 @@ public struct Pager<Element, ID, PageView>: View  where PageView: View, Element:
     /*** Constants ***/
 
     /// Angle of rotation when should rotate
-    let rotationDegrees: Double = 20
+    let rotationDegrees: Double = -90
 
     /// Axis of rotation when should rotate
-    let rotationAxis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0, 1, 0)
+    let rotationAxis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0, 1, 0.001)
 
     /*** Dependencies ***/
 
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/PagerContent.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/PagerContent.swift
index f083cf4..19ac638 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/PagerContent.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/Pager/SwiftUIPager/PagerContent.swift
@@ -26,10 +26,10 @@ extension Pager {
         /*** Constants ***/
 
         /// Angle of rotation when should rotate
-        let rotationDegrees: Double = 20
+        let rotationDegrees: Double = -90
 
         /// Axis of rotation when should rotate
-        let rotationAxis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0, 1, 0)
+        let rotationAxis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0, 1, 0.001)
 
         /*** Dependencies ***/
 
@@ -197,6 +197,9 @@ extension Pager {
                     }
                 }
                 .offset(x: self.xOffset, y : self.yOffset)
+                .onChange(of: pagerModel.index) { index in
+                    onPageChanged?(index)
+                }
             }
             .frame(size: size)
 
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extensions/String+Extension.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extensions/String+Extension.swift
index dce077e..7f026c3 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extensions/String+Extension.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extensions/String+Extension.swift
@@ -19,8 +19,8 @@ extension String {
     }
     
     var isValidURL: Bool {
-        let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
-        if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
+        let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
+        if let match = detector?.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
             // it is a link, if the match covers the whole string
             return match.range.length == self.utf16.count
         } else {
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Components/SocialHome/AmityPostMenuComponent.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Components/SocialHome/AmityPostMenuComponent.swift
index 8beedf4..9ea1a13 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Components/SocialHome/AmityPostMenuComponent.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Components/SocialHome/AmityPostMenuComponent.swift
@@ -28,6 +28,7 @@ public struct AmityCreatePostMenuComponent: AmityComponentView {
     @Binding private var isPresented: Bool
     
     @State private var showPostCreationMenuScaleEffect: Bool = false
+    private let allowAllUserToCreateStory = AmityUIKitManagerInternal.shared.client.getSocialSettings()?.story?.allowAllUserToCreateStory ?? false
     
     public var id: ComponentId {
         .createPostMenu
@@ -80,6 +81,7 @@ public struct AmityCreatePostMenuComponent: AmityComponentView {
                         .onTapGesture {
                             goToStoryCreation()
                         }
+                        .isHidden(!allowAllUserToCreateStory)
                         .accessibilityIdentifier(AccessibilityID.Social.CreatePostMenu.createStoryButton)
                 case .poll:
                     let icon = AmityIcon.createPollMenuIcon
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/CommunityProfile/CommunityProfileViewModel.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/CommunityProfile/CommunityProfileViewModel.swift
index 3b5e2a8..d8d42b3 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/CommunityProfile/CommunityProfileViewModel.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/CommunityProfile/CommunityProfileViewModel.swift
@@ -51,9 +51,6 @@ public class CommunityProfileViewModel: ObservableObject {
         loadStories()
         loadPinnedFeed()
         
-        Task { @MainActor in
-            hasStoryManagePermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: communityId)
-        }
     }
     
     deinit {
@@ -69,6 +66,14 @@ public class CommunityProfileViewModel: ObservableObject {
             
             self?.pendingPostCount = community.pendingPostCount
             
+            // Check StoryManage Permission
+            Task { @MainActor [weak self] in
+                let hasPermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: community.communityId)
+                let allowAllUserCreation = AmityUIKitManagerInternal.shared.client.getSocialSettings()?.story?.allowAllUserToCreateStory ?? false
+               
+                self?.hasStoryManagePermission = (allowAllUserCreation || hasPermission) && community.isJoined
+            }
+            
             if communityObject.onlyAdminCanPost {
                 AmityUIKit4Manager.client.hasPermission(.createPrivilegedPost, forCommunity: community.communityId) { success in
                     self?.hasCreatePostPermission = success && community.isJoined
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/PostDetail/AmityPostDetailPage.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/PostDetail/AmityPostDetailPage.swift
index 31c9fc5..b1f898b 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/PostDetail/AmityPostDetailPage.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Social/Pages/PostDetail/AmityPostDetailPage.swift
@@ -242,7 +242,7 @@ class AmityPostDetailPageViewModel: ObservableObject {
         postObject = postManager.getPost(withId: postId)
         token = postObject?.observe({ livePost, error in
             
-            if let snapshot = livePost.snapshot, let _ = snapshot.getPollInfo() {
+            if let snapshot = livePost.snapshot {
                 self.post = AmityPostModel(post: snapshot)
             }
         })
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Components/StoryTab/AmityStoryTabComponent.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Components/StoryTab/AmityStoryTabComponent.swift
index 4117c4d..e0e5397 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Components/StoryTab/AmityStoryTabComponent.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Components/StoryTab/AmityStoryTabComponent.swift
@@ -163,16 +163,25 @@ class AmityStoryTabComponentViewModel: ObservableObject {
     }
     
     private func loadCommunityStoryTarget(_ communityId: String) {
-        Task { @MainActor in
-            self.hasManagePermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: communityId)
-        }
-        
         storyTargetObject = storyManager.getStoryTarget(targetType: .community, targetId: communityId)
         cancellable = nil
         cancellable = storyTargetObject?.$snapshot
             .sink(receiveValue: { [weak self] target in
                 guard let target else { return }
                 
+                // Check StoryManage Permission
+                Task { @MainActor [weak self] in
+                    let hasPermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: communityId)
+                    let allowAllUserCreation = AmityUIKitManagerInternal.shared.client.getSocialSettings()?.story?.allowAllUserToCreateStory ?? false
+                   
+                    guard let community = target.community else {
+                        self?.hasManagePermission = false
+                        return
+                    }
+                        
+                    self?.hasManagePermission = (allowAllUserCreation || hasPermission) && community.isJoined
+                }
+                
                 if let existingModel = self?.communityFeedStoryTarget {
                     existingModel.updateModel(target)
                 } else {
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Models/AmityStoryTargetModel.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Models/AmityStoryTargetModel.swift
index 1f45003..6338fb6 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Models/AmityStoryTargetModel.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Models/AmityStoryTargetModel.swift
@@ -28,6 +28,7 @@ public class AmityStoryTargetModel: ObservableObject, Identifiable, Equatable {
     
     @Published var items: [PaginatedItem<AmityStoryModel>] = []
     @Published var itemCount: Int = 0
+    @Published var storyLoadingStatus: AmityLoadingStatus = .notLoading
     @Published var hasUnseenStory: Bool = false
     @Published var hasFailedStory: Bool = false
     @Published var hasSyncingStory: Bool = false
@@ -72,6 +73,7 @@ public class AmityStoryTargetModel: ObservableObject, Identifiable, Equatable {
         var surplus = 0
         if let adFrequency = AdEngine.shared.getAdFrequency(at: .story), adFrequency.value > 0 {
             surplus = totalSeenStoryCount % adFrequency.value
+            Log.add(event: .info, "Surplus: \(surplus)")
         }
         
         paginatorCancellable = nil
@@ -81,7 +83,7 @@ public class AmityStoryTargetModel: ObservableObject, Identifiable, Equatable {
         paginator?.load()
         
         paginatorCancellable = paginator?.$snapshots
-            .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
+            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
             .sink(receiveValue: { [weak self] items in
                 guard let self, let stories = self.storyCollection?.snapshots else { return }
                 
@@ -109,10 +111,8 @@ public class AmityStoryTargetModel: ObservableObject, Identifiable, Equatable {
                 }
                 
                 self.items = newSnapshot.items
-                
-                if self.itemCount != newSnapshot.items.count {
-                    self.itemCount = newSnapshot.items.count
-                }
+                self.itemCount = newSnapshot.items.count
+
                 VideoPlayer.preload(urls: newSnapshot.videoURLs)
                 
                 
@@ -121,8 +121,10 @@ public class AmityStoryTargetModel: ObservableObject, Identifiable, Equatable {
                     self.hasFailedStory = false
                     self.hasSyncingStory = false
                 }
+                
+                self.storyLoadingStatus = storyCollection.loadingStatus
             })
-
+        
     }
     
     private func mapToModel(_ items: [PaginatedItem<AmityStory>]) -> (items: [PaginatedItem<AmityStoryModel>], videoURLs: [URL]) {
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPage.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPage.swift
index 18c687a..f8ef01b 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPage.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPage.swift
@@ -10,8 +10,6 @@ import AVKit
 import AmitySDK
 import Combine
 
-let STORY_DURATION: CGFloat = 4.0
-
 public enum AmityViewStoryPageType {
       case communityFeed(String)
       // User can start viewing any specific story target in Global Feed.
@@ -27,404 +25,102 @@ public struct AmityViewStoryPage: AmityPageView {
         .storyPage
     }
 
-    @StateObject var viewModel: AmityStoryPageViewModel
-    
-    @State private var totalDuration: CGFloat = STORY_DURATION
-    
-    @State private var storyTargetIndex = 0
-    @State private var storySegmentIndex: Int = 0
-    @State private var progressSegmentWidth: CGFloat = 0.0
-    @State private var progressValueToIncrease: CGFloat = 0.0
-    @State private var showBottomSheet: Bool = false
-    @State private var isAlertShown: Bool = false
-    @State private var showToast: Bool = false
-    
+    @StateObject var viewModel: AmityViewStoryPageViewModel
     @State private var hasStoryManagePermission: Bool = false
-    @StateObject private var progressBarViewModel: ProgressBarViewModel = ProgressBarViewModel(progressArray: [])
-    @StateObject private var storyCoreViewModel: StoryCoreViewModel = StoryCoreViewModel()
+    @State private var page: Page = Page.first()
         
     private let storyPageType: AmityViewStoryPageType
     
-    @State private var page: Page = Page.first()
     @StateObject private var viewConfig: AmityViewConfigController
     @Environment(\.colorScheme) private var colorScheme
     
     public init(type: AmityViewStoryPageType) {
         self.storyPageType = type
-        _viewModel = StateObject(wrappedValue: AmityStoryPageViewModel(type: type))
+        _viewModel = StateObject(wrappedValue: AmityViewStoryPageViewModel(type: type))
         _viewConfig = StateObject(wrappedValue: AmityViewConfigController(pageId: .storyPage))
     }
     
    
     public var body: some View {
-        
-        AmityView(configId: configId,
-                  config: { configDict in
-        }) { config in
-            GeometryReader { geometry in
-                Pager(page: page, data: viewModel.storyTargets, id: \.targetId) { storyTarget in
-                    ZStack() {
-                        
-                        StoryCoreView(self, storyTarget: storyTarget,
-                                      storySegmentIndex: $storySegmentIndex,
-                                      totalDuration: $totalDuration,
-                                      moveStoryTarget: { direction in moveStoryTarget(direction: direction) },
-                                      moveStorySegment: { direction in moveStorySegment(direction: direction) })
-                            .environmentObject(viewModel)
-                            .environmentObject(storyCoreViewModel)
-                            .environmentObject(host)
-
-                        
-                        VStack(alignment: .trailing) {
-                            ProgressBarView(pageId: id, progressBarViewModel: progressBarViewModel)
-                                .frame(height: 3)
-                                .padding(EdgeInsets(top: 16, leading: 20, bottom: 10, trailing: 20))
-                                .animation(nil)
-                                .onAppear {
-                                    Log.add(event: .info, "ProgressBar Appeared")
-                                }
-                                .onReceive(storyTarget.$itemCount) { count in                                    
-                                    Log.add(event: .info, "Story Segment Changed: \(count) - \(storyTarget.targetName)")
-                                    updateProgressSegmentWidth(totalWidth: geometry.size.width, numberOfStories: count)
-                                    
-                                    if storyTarget.hasUnseenStory {
-                                        storySegmentIndex = storyTarget.unseenStoryIndex
-                                    }
-                                    
-                                    progressBarViewModel.progressArray = (0..<count).map({ index in
-                                        // If a story is deleted and story count is changed, this block will trigger again.
-                                        // All segments till storySegmentIndex need to be filled.
-                                        if index < storySegmentIndex && storySegmentIndex != 0 {
-                                            let model = AmityProgressBarElementViewModel()
-                                            model.progress = progressSegmentWidth
-                                            return model
-                                        }
-                                        return AmityProgressBarElementViewModel()
-                                    })
-                                }
-                                .isHidden(viewConfig.isHidden(elementId: .progressBarElement), remove: false)
-
-                            HStack(spacing: 0) {
-                                /// Show overflow menu if item is the story
-                                /// Hide it if item is ads
-                                if let item = viewModel.storyTargets[storyTargetIndex].items.element(at: storySegmentIndex),
-                                   case let .content(_) = item.type {
-                                    Button {
-                                        showBottomSheet.toggle()
-                                    } label: {
-                                        let icon = AmityIcon.getImageResource(named: viewConfig.getConfig(elementId: .overflowMenuElement, key: "overflow_menu_icon", of: String.self) ?? "")
-                                        Image(icon)
-                                            .frame(width: 24, height: 20)
-                                            .padding(.trailing, 20)
-                                    }
-                                    .onAppear {
-                                        Task {
-                                            let storyTargetId = viewModel.storyTargets[storyTargetIndex].targetId
-                                            hasStoryManagePermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: storyTargetId)
-                                        }
-                                    }
-                                    .isHidden(!hasStoryManagePermission, remove: false)
-                                    .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.meatballsButton)
-                                    .isHidden(viewConfig.isHidden(elementId: .overflowMenuElement), remove: false)
-                                }
-                                
-                                Button {
-                                    Log.add(event: .info, "Tapped Closed!!!")
-                                    host.controller?.dismiss(animated: true)
-                                } label: {
-                                    let icon = AmityIcon.getImageResource(named: viewConfig.getConfig(elementId: .closeButtonElement, key: "close_icon", of: String.self) ?? "")
-                                    Image(icon)
-                                        .frame(width: 24, height: 18)
-                                        .padding(.trailing, 25)
-                                }
-                                .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.closeButton)
-                                .isHidden(viewConfig.isHidden(elementId: .closeButtonElement), remove: false)
-                            }
-                            
-                            Spacer()
-                        }
-                    }
-                }
-                .contentLoadingPolicy(.lazy(recyclingRatio: 1))
-                .bottomSheet(isPresented: $showBottomSheet, height: 148, topBarBackgroundColor: Color(viewConfig.theme.backgroundColor), animation: .easeIn(duration: 0.1)) {
-                    getBottomSheetView()
-                }
-                .onAppear {
-                    page.update(.new(index: storyTargetIndex))
-                }
-                .onDisappear {
-                    viewModel.shouldRunTimer =  false
-                }
-                .onReceive(viewModel.timer, perform: { _ in
-                    guard viewModel.shouldRunTimer else { return }
-                    timerAction()
-                })
-                .onChange(of: showBottomSheet) { value in
-                    viewModel.shouldRunTimer = !value
-                    storyCoreViewModel.playVideo = !value
-                }
-                .onChange(of: isAlertShown) { value in
-                    viewModel.shouldRunTimer = !value
-                    storyCoreViewModel.playVideo = !value
-                }
-                .onChange(of: showToast) { value in
-                    Toast.showToast(style: viewModel.toastStyle, message: viewModel.toastMessage, bottomPadding: 70)
-                }
-                .onReceive(viewModel.$storyTargets) { value in
+        ZStack {
+            Color.clear
+                .overlay (
+                    ProgressView().progressViewStyle(CircularProgressViewStyle(tint: .white))
+                        .isHidden(!viewModel.showActivityIndicator)
+                )
+                .overlay(
+                    closeButton
+                        .padding(.top, 40)
+                        .isHidden(viewModel.storyTargets.element(at: viewModel.storyTargetIndex)?.itemCount != 0)
+                , alignment: .topTrailing)
+                .onReceive(viewModel.$storyTargetLoadingStatus) { status in
                     if case .globalFeed(let communityId) = storyPageType {
-                        storyTargetIndex = value.firstIndex { $0.targetId == communityId } ?? 0
+                        let index = viewModel.storyTargets.firstIndex { $0.targetId == communityId } ?? 0
+                        viewModel.storyTargetIndex = index
+                        page.update(.new(index: index))
                     }
-                }
-                .onChange(of: storyTargetIndex) { value in
-                    viewModel.preloadStoryTargets(value, viewModel.storyTargets)
-                    storySegmentIndex = 0
-                    page.update(.new(index: value))
-                    updateProgressSegmentWidth(totalWidth: geometry.size.width, numberOfStories: viewModel.storyTargets[value].items.count)
                     
+                    if case .communityFeed(_) = storyPageType {
+                        viewModel.storyTargetIndex = 0
+                        page.update(.new(index: 0))
+                    }
                 }
-                .onChange(of: totalDuration) { value in
-                    progressValueToIncrease = progressSegmentWidth / CGFloat(value / 0.001)
+                .zIndex(1)
+            
+            if viewModel.storyTargetLoadingStatus == .loaded || !viewModel.storyTargets.isEmpty {
+                Pager(page: page, data: viewModel.storyTargets, id: \.targetId) { storyTarget in
+                    StoryCoreView(self,
+                                  storyPageViewModel: viewModel,
+                                  storyTarget: storyTarget)
+                    .environmentObject(host)
+                }
+                .contentLoadingPolicy(.lazy(recyclingRatio: 4))
+                .interactive(rotation: true)
+                .interactive(scale: 0.7)
+                .itemSpacing(-60)
+                .horizontal()
+                .allowsDragging(viewModel.storyTargets.count != 1)
+                .pagingPriority(.simultaneous)
+                .sensitivity(.custom(0.35))
+                .delaysTouches(false)
+                .onDraggingBegan({
+                    viewModel.debounceUpdateShouldRunTimer(false)
+                })
+                .onDraggingEnded({
+                    viewModel.debounceUpdateShouldRunTimer(true)
+                })
+                .draggingAnimation(onChange: .custom(animation: .easeInOut(duration: 0.3)), onEnded: .custom(animation: .easeInOut(duration: 0.3)))
+                .onPageChanged({ index in
+                    viewModel.debounceUpdateStoryTargetIndex(index)
+                })
+                .onChange(of: viewModel.storyTargetIndex) { index in
+                    guard page.index != index else { return }
+                    page.update(.new(index: index))
                 }
-                .onChange(of: storySegmentIndex) { _ in
-
+                .onDisappear {
+                    viewModel.shouldRunTimer =  false
                 }
-                .tabViewStyle(.page(indexDisplayMode: .never))
             }
         }
         .environmentObject(viewConfig)
-        .background(Color.black.ignoresSafeArea())
-        .ignoresSafeArea(.keyboard)
+        .background(Color.black.ignoresSafeArea(edges: .top))
+        .ignoresSafeArea(edges: .bottom)
+        .statusBarHidden()
         .onChange(of: colorScheme) { value in
             viewConfig.updateTheme()
         }
     }
     
-    
     @ViewBuilder
-    private func getBottomSheetView() -> some View {
-        VStack {
-            HStack(spacing: 12) {
-                Image(AmityIcon.trashBinIcon.getImageResource())
-                    .renderingMode(.template)
-                    .resizable()
-                    .aspectRatio(contentMode: .fill)
-                    .frame(width: 20, height: 24)
-                    .foregroundColor(Color(viewConfig.theme.baseColor))
-                
-                Button {
-                    isAlertShown.toggle()
-                    showBottomSheet.toggle()
-                } label: {
-                    Text("Delete story")
-                        .applyTextStyle(.bodyBold(Color(viewConfig.theme.baseColor)))
-                }
-                .buttonStyle(.plain)
-                .alert(isPresented: $isAlertShown, content: {
-                    Alert(title: Text(AmityLocalizedStringSet.Story.deleteStoryTitle.localizedString), message: Text(AmityLocalizedStringSet.Story.deleteStoryMessage.localizedString), primaryButton: .cancel(), secondaryButton: .destructive(Text(AmityLocalizedStringSet.General.delete.localizedString), action: {
-                        if let item = viewModel.storyTargets[storyTargetIndex].items.element(at: storySegmentIndex),
-                            case let .content(story) = item.type {
-                            Task { @MainActor in
-                                try await viewModel.deleteStory(storyId: story.storyId)
-                                storyCoreViewModel.playVideo = false
-                                moveStorySegment(direction: .backward)
-                                showToast.toggle()
-                            }
-                        }
-                    }))
-                })
-                .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.BottomSheet.deleteButton)
-                Spacer()
-            }
-            .padding(EdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20))
-            Spacer()
-        }
-        .background(Color(viewConfig.theme.backgroundColor).ignoresSafeArea())
-    }
-    
-    
-    private func updateProgressSegmentWidth(totalWidth: CGFloat, numberOfStories: Int) {
-        progressSegmentWidth = (totalWidth - 37 - (3.0 * CGFloat(numberOfStories))) / CGFloat(numberOfStories)
-        progressValueToIncrease = progressSegmentWidth / CGFloat(totalDuration / 0.001)
-    }
-    
-    
-    private func timerAction() {
-        
-        guard storySegmentIndex < progressBarViewModel.progressArray.count else {
+    private var closeButton: some View {
+        Button {
+            Log.add(event: .info, "Tapped Closed!!!")
             host.controller?.dismiss(animated: true)
-            return
-        }
-        
-        if progressBarViewModel.progressArray[storySegmentIndex].progress == progressSegmentWidth {
-            
-            let lastStoryTargetIndex = viewModel.storyTargets.count - 1
-            let lastStorySegmentIndex = viewModel.storyTargets[storyTargetIndex].items.count - 1
-            
-            
-            if storySegmentIndex != lastStorySegmentIndex {
-                storySegmentIndex += 1
-            } else {
-
-                if storyTargetIndex != lastStoryTargetIndex {
-                    storyTargetIndex += 1
-                } else {
-                    host.controller?.dismiss(animated: true)
-                }
-                
-            }
-         
-        } else {
-            let oldProgress = progressBarViewModel.progressArray[storySegmentIndex].progress
-            let newProgress = oldProgress + progressValueToIncrease
-            progressBarViewModel.progressArray[storySegmentIndex].progress = min(max(oldProgress, newProgress), progressSegmentWidth)
-        }
-    }
-    
-    
-    func moveStorySegment(direction: MoveDirection) {
-        guard storySegmentIndex < progressBarViewModel.progressArray.count else {
-            return
-        }
-
-        switch direction {
-        case .forward:
-            progressBarViewModel.progressArray[storySegmentIndex].progress = progressSegmentWidth
-            
-            let lastStoryTargetIndex = viewModel.storyTargets.count - 1
-            let lastStorySegmentIndex = viewModel.storyTargets[storyTargetIndex].items.count - 1
-            
-            if storySegmentIndex != lastStorySegmentIndex {
-                storySegmentIndex += 1
-            } else if storyTargetIndex == lastStoryTargetIndex && storySegmentIndex == lastStorySegmentIndex {
-                host.controller?.dismiss(animated: true)
-            }
-        
-        case .backward:
-            progressBarViewModel.progressArray[storySegmentIndex].progress = 0
-            
-            if storySegmentIndex >= 1 {
-                storySegmentIndex -= 1
-                progressBarViewModel.progressArray[storySegmentIndex].progress  = 0
-            } else if storyTargetIndex >= 1 {
-                storyTargetIndex -= 1
-            }
-        }
-    }
-    
-    
-    func moveStoryTarget(direction: MoveDirection) {
-        switch direction {
-        case .forward:
-            guard storyTargetIndex < viewModel.storyTargets.count - 1 else { return }
-            storyTargetIndex += 1
-        case .backward:
-            guard storyTargetIndex > 0 else { return }
-            storyTargetIndex -= 1
-        }
-    }
-}
-
-enum MoveDirection {
-    case forward, backward
-}
-
-class AmityStoryPageViewModel: ObservableObject {
-    
-    @Published var storyTargets: [AmityStoryTargetModel] = []
-    @Published var toastMessage: String = ""
-    @Published var toastStyle: ToastStyle = .success
-    private var seenStoryCount: Int = 0
-    
-    private var cancellable: AnyCancellable?
-    private var storyTargetObject: AmityObject<AmityStoryTarget>?
-    private var storyTargetCollection: AmityCollection<AmityStoryTarget>?
-    private var seenStoryModels: Set<AmityStoryModel> = Set()
-    
-    let storyManager = StoryManager()
-    let timer = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
-    var shouldRunTimer: Bool = false
-    
-    init(type: AmityViewStoryPageType) {
-        
-        switch type {
-        case .communityFeed(let communityId):
-            storyTargetObject = storyManager.getStoryTarget(targetType: .community, targetId: communityId)
-            cancellable = nil
-            cancellable = storyTargetObject?.$snapshot
-                .first { [weak self] _ in
-                    self?.storyTargetObject?.dataStatus == .fresh
-                }
-                .sink { [weak self] target in
-                    guard let target else { return }
-                    
-                    let storyTarget = AmityStoryTargetModel(target)
-                    storyTarget.fetchStory()
-                    
-                    self?.storyTargets = [storyTarget]
-                }
-            
-        case .globalFeed(let communityId):
-            storyTargetCollection = storyManager.getGlobaFeedStoryTargets(options: .smart)
-            cancellable = nil
-            cancellable = storyTargetCollection?.$snapshots
-                .first { !$0.isEmpty }
-                .sink { [weak self] targets in
-                    let storyTargets = targets.compactMap { target -> AmityStoryTargetModel? in
-                        return AmityStoryTargetModel(target)
-                    }.removeDuplicates()
-                    self?.preloadStoryTargets(storyTargets.firstIndex { $0.targetId == communityId } ?? 0, storyTargets)
-                    
-                    self?.storyTargets = storyTargets
-                }
-        }
-    }
-    
-    deinit {
-        seenStoryModels.forEach { $0.analytics.markAsSeen() }
-        timer.upstream.connect().cancel()
-        URLImageService.defaultImageService.inMemoryStore?.removeAllImages()
-    }
-    
-    func preloadStoryTargets(_ index: Int, _ storyTargets: [AmityStoryTargetModel]) {
-        let nextStoryTargetIndex = index + 1
-        let previousStoryTargetIndex = index - 1
-        
-        if index <= storyTargets.count - 1 {
-            storyTargets[index].fetchStory(totalSeenStoryCount: seenStoryCount)
-        }
-        
-        if nextStoryTargetIndex <= storyTargets.count - 1 {
-            Log.add(event: .info, "Preloaded next index: \(nextStoryTargetIndex)")
-            storyTargets[nextStoryTargetIndex].fetchStory(totalSeenStoryCount: seenStoryCount)
-        }
-        
-        if previousStoryTargetIndex >= 0 {
-            Log.add(event: .info, "Preloaded previous index: \(previousStoryTargetIndex)")
-            storyTargets[previousStoryTargetIndex].fetchStory(totalSeenStoryCount: seenStoryCount)
+        } label: {
+            let icon = AmityIcon.getImageResource(named: viewConfig.getConfig(elementId: .closeButtonElement, key: "close_icon", of: String.self) ?? "")
+            Image(icon)
+                .frame(width: 24, height: 18)
+                .padding(.trailing, 25)
         }
     }
-    
-    func markAsSeen(_ storyModel: AmityStoryModel) {
-        seenStoryModels.insert(storyModel)
-        seenStoryCount += 1
-    }
-    
-    @MainActor
-    func deleteStory(storyId: String) async throws {
-        try await storyManager.deleteStory(storyId: storyId)
-        toastMessage = AmityLocalizedStringSet.Story.storyDeletedToastMessage.localizedString
-    }
-    
-    @MainActor 
-    @discardableResult
-    func addReaction(storyId: String) async throws -> Bool {
-        try await storyManager.addReaction(storyId: storyId)
-    }
-    
-    @MainActor
-    @discardableResult
-    func removeReaction(storyId: String) async throws -> Bool {
-        try await storyManager.removeReaction(storyId: storyId)
-    }
-    
 }
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPageViewModel.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPageViewModel.swift
new file mode 100644
index 0000000..6ef41d3
--- /dev/null
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/AmityViewStoryPageViewModel.swift
@@ -0,0 +1,109 @@
+//
+//  AmityStoryPageViewModel.swift
+//  AmityUIKit4
+//
+//  Created by Zay Yar Htun on 9/3/24.
+//
+
+import SwiftUI
+import AmitySDK
+import Combine
+
+let STORY_DURATION: CGFloat = 4.0
+
+class AmityViewStoryPageViewModel: ObservableObject {
+    @Published var storyTargetIndex: Int = 0
+    
+    @Published var storyTargets: [AmityStoryTargetModel] = []
+    @Published var totalDuration: CGFloat = STORY_DURATION
+    @Published var storyTargetLoadingStatus: AmityLoadingStatus = .notLoading
+    @Published var shouldRunTimer: Bool = false
+    @Published var showActivityIndicator: Bool = true
+    var seenStoryCount: Int = 0
+    
+    private var cancellable = Set<AnyCancellable>()
+    private var storyTargetObject: AmityObject<AmityStoryTarget>?
+    private var storyTargetCollection: AmityCollection<AmityStoryTarget>?
+    private var seenStoryModels: Set<AmityStoryModel> = Set()
+    private let updateShouldRunTimedebouncer = Debouncer(delay: 0.1)
+    private let storyTargetMovingDebouncer = Debouncer(delay: 0.3)
+    
+    let storyManager = StoryManager()
+    let timer = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
+    
+    var activeStoryTarget: AmityStoryTargetModel {
+        storyTargets[storyTargetIndex]
+    }
+    
+    init(type: AmityViewStoryPageType) {
+        
+        switch type {
+        case .communityFeed(let communityId):
+            storyTargetObject = storyManager.getStoryTarget(targetType: .community, targetId: communityId)
+            storyTargetObject?.$snapshot
+                .first()
+                .sink { [weak self] target in
+                    guard let self, let target else { return }
+                    
+                    let storyTarget = AmityStoryTargetModel(target)
+                    self.storyTargets = [storyTarget]
+                }
+                .store(in: &cancellable)
+            
+            storyTargetObject?.$loadingStatus
+                .sink(receiveValue: { [weak self] status in
+                    guard let self else { return }
+                    self.storyTargetLoadingStatus = status
+                })
+                .store(in: &cancellable)
+            
+        case .globalFeed(_):
+            storyTargetCollection = storyManager.getGlobaFeedStoryTargets(options: .smart)
+            storyTargetCollection?.$snapshots
+                .first { !$0.isEmpty }
+                .sink { [weak self] targets in
+                    guard let self else { return }
+                    
+                    let storyTargets = targets.compactMap { target -> AmityStoryTargetModel? in
+                        return AmityStoryTargetModel(target)
+                    }.removeDuplicates()
+                    
+                    self.storyTargets = storyTargets
+                }
+                .store(in: &cancellable)
+                    
+            storyTargetCollection?.$loadingStatus
+                .sink(receiveValue: { [weak self] status in
+                    guard let self else { return }
+                    self.storyTargetLoadingStatus = status
+                })
+                .store(in: &cancellable)
+        }
+    }
+    
+    deinit {
+        timer.upstream.connect().cancel()
+        URLImageService.defaultImageService.inMemoryStore?.removeAllImages()
+    }
+    
+    func markAsSeen(_ storyModel: AmityStoryModel) {
+        if !storyModel.isSeen {
+            storyModel.analytics.markAsSeen()
+        }
+        seenStoryCount += 1
+    }
+    
+    func debounceUpdateShouldRunTimer(_ value: Bool) {
+        updateShouldRunTimedebouncer.run { [weak self] in
+            guard self?.shouldRunTimer != value else { return }
+            self?.shouldRunTimer = value
+        }
+    }
+    
+    func debounceUpdateStoryTargetIndex(_ value: Int) {
+        storyTargetMovingDebouncer.run { [weak self] in
+            guard self?.storyTargetIndex != value else { return }
+            self?.storyTargetIndex = value
+        }
+    }
+}
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/ProgressBarView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/ProgressBarView.swift
index 57512e1..ba31965 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/ProgressBarView.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/ProgressBarView.swift
@@ -19,18 +19,26 @@ struct ProgressBarView: View {
             HStack(spacing: spacing) {
                 ForEach(0..<progressBarViewModel.progressArray.count, id: \.self) { index in
                     AmityProgressBarElement(pageId: pageId, progressBarViewModel: progressBarViewModel.progressArray[index])
-                }
-                .onAppear {
-                    Log.add(event: .info, "ProgressBar Stack Appeared")
+                        .onAppear {
+                            updateSegmentFullProgress(geometry)
+                        }
+                        .onChange(of: progressBarViewModel.progressArray.count) { _ in
+                            updateSegmentFullProgress(geometry)
+                        }
                 }
             }
         }
     }
+    
+    private func updateSegmentFullProgress(_ geometry: GeometryProxy) {
+        progressBarViewModel.segmentFullProgress = (geometry.size.width - (3.0 * CGFloat(progressBarViewModel.progressArray.count))) / CGFloat(progressBarViewModel.progressArray.count)
+    }
 }
 
 
 class ProgressBarViewModel: ObservableObject {
     @Published var progressArray: [AmityProgressBarElementViewModel]
+    fileprivate(set) var segmentFullProgress: CGFloat = 0.0
     
     init(progressArray: [AmityProgressBarElementViewModel]) {
         self.progressArray = progressArray
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryAdView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryAdView.swift
index 089b4d2..d91baba 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryAdView.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryAdView.swift
@@ -10,7 +10,7 @@ import SwiftUI
 
 struct StoryAdView<Content: View>: View {
     @EnvironmentObject private var viewConfig: AmityViewConfigController
-    @EnvironmentObject var storyPageViewModel: AmityStoryPageViewModel
+    @EnvironmentObject var storyPageViewModel: AmityViewStoryPageViewModel
     @EnvironmentObject var storyCoreViewModel: StoryCoreViewModel
     @State private var showAdInfo: Bool = false
     let ad: AmityAd
@@ -131,10 +131,10 @@ struct StoryAdView<Content: View>: View {
         .sheet(isPresented: $showAdInfo, content: {
             AmityAdInfoView(advertiserName: ad.advertiser?.companyName ?? "-")
         })
-        .onChange(of: showAdInfo) { isShown in
-            storyPageViewModel.shouldRunTimer = !isShown
-            storyCoreViewModel.playVideo = !isShown
-        }
+//        .onChange(of: showAdInfo) { isShown in
+//            storyPageViewModel.shouldRunTimer = !isShown
+//            storyCoreViewModel.playVideo = !isShown
+//        }
         .onAppear {
             ad.analytics.markAsSeen(placement: .story)
         }
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreView.swift
index 5db6546..0e6dceb 100644
--- a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreView.swift
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreView.swift
@@ -6,9 +6,9 @@
 //
 
 import SwiftUI
-import AVKit
 import AmitySDK
 import Combine
+import AVKit
 
 struct StoryCoreView: View, AmityViewIdentifiable {
     var viewStoryPage: AmityViewStoryPage
@@ -18,84 +18,184 @@ struct StoryCoreView: View, AmityViewIdentifiable {
     
     @EnvironmentObject var host: AmitySwiftUIHostWrapper
     @ObservedObject var storyTarget: AmityStoryTargetModel
-    @EnvironmentObject var storyPageViewModel: AmityStoryPageViewModel
-    @EnvironmentObject var storyCoreViewModel: StoryCoreViewModel
-    
-    @Binding var storySegmentIndex: Int
-    @Binding var totalDuration: CGFloat
+    @ObservedObject var storyPageViewModel: AmityViewStoryPageViewModel
+    @StateObject private var viewModel: StoryCoreViewModel
+ 
     @State private var muteVideo: Bool = false
     @State private var showRetryAlert: Bool = false
     @State private var showCommentTray: Bool = false
-    
+    @State private var showBottomSheet: Bool = false
+    @State private var isAlertShown: Bool = false
+    @StateObject private var page = Page.first()
     @State private var hasStoryManagePermission: Bool = false
-    
-    var moveStoryTarget: ((MoveDirection) -> Void)?
-    var moveStorySegment: ((MoveDirection) -> Void)?
-    
-    @State private var page: Page = Page.first()
     @EnvironmentObject private var viewConfig: AmityViewConfigController
     
-    init(_ viewStoryPage: AmityViewStoryPage, storyTarget: AmityStoryTargetModel,
-         storySegmentIndex: Binding<Int>,
-         totalDuration: Binding<CGFloat>,
-         moveStoryTarget: ((MoveDirection) -> Void)? = nil,
-         moveStorySegment: ((MoveDirection) -> Void)? = nil) {
+    private var isActiveTarget: Bool {
+        storyPageViewModel.activeStoryTarget == storyTarget
+    }
+    
+    init(_ viewStoryPage: AmityViewStoryPage,
+         storyPageViewModel: AmityViewStoryPageViewModel,
+         storyTarget: AmityStoryTargetModel) {
         self.viewStoryPage = viewStoryPage
         self.storyTarget = storyTarget
-        self._storySegmentIndex = storySegmentIndex
-        self._totalDuration = totalDuration
         self.targetName = storyTarget.targetName
         self.avatar = storyTarget.avatar
         self.isVerified = storyTarget.isVerifiedTarget
-        self.moveStoryTarget = moveStoryTarget
-        self.moveStorySegment = moveStorySegment
+       
+        self.storyPageViewModel = storyPageViewModel
+        self._viewModel = StateObject(wrappedValue: StoryCoreViewModel(storyTarget, storyPageViewModel: storyPageViewModel))
     }
     
     var body: some View {
-        Pager(page: page, data: storyTarget.items, id: \.id) { item in
-            switch item.type {
-            case .ad(let ad):
-                StoryAdView(ad: ad, gestureView: getGestureView)
-            case .content(let storyModel):
-                getStoryView(storyModel)
+        ZStack(alignment: .top) {
+            Pager(page: page, data: storyTarget.items, id: \.id) { item in
+                getContentView(item)
+                    .environmentObject(storyPageViewModel)
+                    .environmentObject(viewModel)
+            }
+            .contentLoadingPolicy(.lazy(recyclingRatio: 1))
+            .disableDragging()
+            .onPageChanged { segmentIndex in
+                viewModel.storySegmentIndex = segmentIndex
+                guard isActiveTarget else { return }
+                
+                viewModel.playIfVideoStory()
+                
+                // Mark Seen
+                if let item = storyTarget.items.element(at: viewModel.storySegmentIndex), case .content(let storyModel) = item.type {
+                    storyPageViewModel.markAsSeen(storyModel)
+                }
             }
+            .onChange(of: viewModel.storySegmentIndex) { segmentIndex in
+                guard page.index != segmentIndex else { return }
+                page.update(.new(index: segmentIndex))
+            }
+            
+            ProgressBarView(pageId: .storyPage, progressBarViewModel: viewModel.progressBarViewModel)
+                .frame(height: 3)
+                .padding(EdgeInsets(top: 16, leading: 20, bottom: 10, trailing: 20))
+                .onReceive(storyTarget.$itemCount) { count in
+                    guard count != viewModel.progressBarViewModel.progressArray.count else { return }
+                    viewModel.createProgressBarViewModelProgressArray(count)
+                    
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
+                        viewModel.updateProgressBarViewModelProgressArray(viewModel.storySegmentIndex)
+                    }
+                }
+                .isHidden(viewConfig.isHidden(elementId: .progressBarElement), remove: false)
         }
-        .contentLoadingPolicy(.lazy(recyclingRatio: 1))
-        .overlay (
-            ProgressView().progressViewStyle(CircularProgressViewStyle(tint: .white))
-                .offset(y: -40)
-                .isHidden(storyTarget.items.count != 0, remove: false)
-        )
-        .onAppear {
-            page.update(.new(index: storySegmentIndex))
+        .onChange(of: storyPageViewModel.storyTargetIndex) { targetIndex in
+            viewModel.updateProgressBarViewModelProgressArray(viewModel.storySegmentIndex)
+            viewModel.stopVideo()
             
+            guard isActiveTarget else { return }
+            viewModel.storyTarget.fetchStory(totalSeenStoryCount: storyPageViewModel.seenStoryCount)
+            viewModel.playIfVideoStory()
+            
+            // Mark Seen
+            if let item = storyTarget.items.element(at: viewModel.storySegmentIndex), case .content(let storyModel) = item.type {
+                storyPageViewModel.markAsSeen(storyModel)
+            }
+        }
+        .onReceive(storyTarget.$unseenStoryIndex) { index in
+            viewModel.moveToUnseenStory(index)
+        }
+        .onReceive(storyPageViewModel.timer, perform: { _ in
+            guard storyPageViewModel.shouldRunTimer, isActiveTarget else { return }
+            guard storyTarget.storyLoadingStatus == .loaded || !storyTarget.items.isEmpty else { return }
+            guard viewModel.isLoadedIfVideoStory() else { return }
+            
+            viewModel.timerAction(host)
+        })
+        .onChange(of: storyPageViewModel.shouldRunTimer) { shouldRunTimer in
+            guard isActiveTarget else { return }
+            viewModel.playPauseVideo(shouldRunTimer)
+        }
+        .onAppear {
+            storyTarget.fetchStory()
+            checkStoryPermission()
+            Log.add(event: .info, "StoryTarget: \(storyTarget.targetName) appeared!!!")
             // Handle the case going back from Community Page
             host.controller?.navigationController?.navigationBar.isHidden = true
         }
-        .onChange(of: storySegmentIndex) { value in
-            page.update(.new(index: value))
+        .onDisappear {
+            Log.add(event: .info, "StoryTarget: index \(storyTarget.targetName) disappeared!!!")
+        }
+        .onChange(of: showBottomSheet) { value in
+            storyPageViewModel.debounceUpdateShouldRunTimer(!value)
+        }
+        .onChange(of: isAlertShown) { value in
+            storyPageViewModel.debounceUpdateShouldRunTimer(!value)
         }
         .onChange(of: showRetryAlert) { value in
-            storyPageViewModel.shouldRunTimer = !value
-            storyCoreViewModel.playVideo = !value
+            storyPageViewModel.debounceUpdateShouldRunTimer(!value)
         }
         .onChange(of: showCommentTray) { value in
-            storyPageViewModel.shouldRunTimer = !value
-            storyCoreViewModel.playVideo = !value
+            storyPageViewModel.debounceUpdateShouldRunTimer(!value)
             
             if value == false {
                 hideKeyboard()
             }
         }
+        .padding(.bottom, 25)
         .sheet(isPresented: $showCommentTray) {
             getCommentSheetView()
         }
-        .gesture(DragGesture().onChanged{ _ in})
+        .bottomSheet(isShowing: $showBottomSheet, height: .contentSize, sheetContent: {
+            getBottomSheetView()
+        })
         .environmentObject(viewConfig)
         .environmentObject(host)
         .animation(nil)
     }
     
+    @ViewBuilder
+    func getContentView(_ item: PaginatedItem<AmityStoryModel>) -> some View {
+        GeometryReader { geometry in
+            ZStack(alignment: .topTrailing) {
+                
+                switch item.type {
+                case .ad(let ad):
+                    StoryAdView(ad: ad, gestureView: getGestureView)
+                case .content(let storyModel):
+                    getStoryView(storyModel)
+                }
+                
+                HStack(spacing: 0) {
+                    /// Show overflow menu if item is the story
+                    /// Hide it if item is ads
+                    if case .content(_) = item.type {
+                        Button {
+                            showBottomSheet.toggle()
+                        } label: {
+                            let icon = AmityIcon.getImageResource(named: viewConfig.getConfig(elementId: .overflowMenuElement, key: "overflow_menu_icon", of: String.self) ?? "")
+                            Image(icon)
+                                .frame(width: 24, height: 20)
+                                .padding(.trailing, 20)
+                        }
+                        .isHidden(!hasStoryManagePermission, remove: false)
+                        .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.meatballsButton)
+                        .isHidden(viewConfig.isHidden(elementId: .overflowMenuElement), remove: false)
+                    }
+                    
+                    Button {
+                        Log.add(event: .info, "Tapped Closed!!!")
+                        host.controller?.dismiss(animated: true)
+                    } label: {
+                        let icon = AmityIcon.getImageResource(named: viewConfig.getConfig(elementId: .closeButtonElement, key: "close_icon", of: String.self) ?? "")
+                        Image(icon)
+                            .frame(width: 24, height: 18)
+                            .padding(.trailing, 25)
+                    }
+                    .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.closeButton)
+                    .isHidden(viewConfig.isHidden(elementId: .closeButtonElement), remove: false)
+                }
+                .padding(.top, 40)
+            }
+        }
+    }
+    
     
     func getStoryView(_ storyModel: AmityStoryModel) -> some View {
         VStack(spacing: 0) {
@@ -103,18 +203,36 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                 GeometryReader { geometry in
                     if let imageURL = storyModel.imageURL {
                         StoryImageView(imageURL: imageURL,
-                                  totalDuration: $totalDuration,
-                                  displayMode: storyModel.imageDisplayMode,
-                                  size: geometry.size)
+                                       displayMode: storyModel.imageDisplayMode,
+                                       size: geometry.size,
+                                       onLoading: {
+                            guard isActiveTarget else { return }
+                            storyPageViewModel.shouldRunTimer = false
+                            storyPageViewModel.showActivityIndicator = true
+                        }, onLoaded: {
+                            guard isActiveTarget else { return }
+                            storyPageViewModel.shouldRunTimer = true
+                            storyPageViewModel.showActivityIndicator = false
+                        })
                         .frame(width: geometry.size.width, height: geometry.size.height)
                         .overlay(
                             storyModel.syncState == .error || storyModel.syncState == .syncing ? Color.black.opacity(0.5) : nil
                         )
                     } else if let videoURL = storyModel.videoURL {
                         StoryVideoView(videoURL: videoURL,
-                                  totalDuration: $totalDuration,
-                                  muteVideo: $muteVideo,
-                                  playVideo: $storyCoreViewModel.playVideo)
+                                       muteVideo: $muteVideo,
+                                       time: $viewModel.playTime,
+                                       playVideo: $viewModel.playVideo, onLoading: {
+                            guard isActiveTarget else { return }
+                            viewModel.isVideoLoading = true
+                            storyPageViewModel.showActivityIndicator = true
+                        }, onPlaying: { videoDuration in
+                            guard isActiveTarget else { return }
+                            viewModel.isVideoLoading = false
+                            storyPageViewModel.shouldRunTimer = true
+                            storyPageViewModel.showActivityIndicator = false
+                            storyPageViewModel.totalDuration = videoDuration
+                        })
                         .frame(width: geometry.size.width, height: geometry.size.height)
                         .overlay(
                             storyModel.syncState == .error || storyModel.syncState == .syncing ? Color.black.opacity(0.5) : nil
@@ -142,9 +260,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                         getHyperLinkView(data: model, story: storyModel)
                     }
                 }
-                .offset(y: 30) // height + padding top, bottom of progressBarView
-                
-                
+                .padding(.top, 30)
             }
             
             if storyModel.syncState == .error {
@@ -156,16 +272,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                 getAnalyticView(storyModel)
             }
         }
-        .onAppear {
-            storyPageViewModel.markAsSeen(storyModel)
-            
-            switch storyModel.storyType {
-            case .image:
-                storyCoreViewModel.playVideo = false
-            case .video:
-                storyCoreViewModel.playVideo = true
-            }
-        }
+        .environmentObject(viewModel)
     }
     
     
@@ -190,11 +297,6 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                     AmityUIKit4Manager.behaviour.viewStoryPageBehaviour?.goToCreateStoryPage(context: context)
                 }
             }
-            .onAppear {
-                Task {
-                    hasStoryManagePermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: story.targetId)
-                }
-            }
             
             VStack(alignment: .leading) {
                 HStack(alignment: .center) {
@@ -283,22 +385,20 @@ struct StoryCoreView: View, AmityViewIdentifiable {
     
     func getGestureView() -> some View {
         GestureView(onLeftTap: {
-            moveStorySegment?(.backward)
+            viewModel.moveStorySegment(direction: .backward, host)
         }, onRightTap: {
-            moveStorySegment?(.forward)
+            viewModel.moveStorySegment(direction: .forward, host)
         }, onTouchAndHoldStart: {
             storyPageViewModel.shouldRunTimer = false
-            storyCoreViewModel.playVideo = false
         }, onTouchAndHoldEnd: {
             storyPageViewModel.shouldRunTimer = true
-            storyCoreViewModel.playVideo = true
-        },onDragChanged: { direction, translation in
+        }, onDragChanged: { direction, translation in
             guard let view = host.controller?.view else { return }
+            guard direction == .downward || direction == .upward else { return }
             
             if translation.y < 0 || (translation.y > 0 && translation.y <= 50) { return }
             
-            storyPageViewModel.shouldRunTimer = false
-            storyCoreViewModel.playVideo = false
+            storyPageViewModel.debounceUpdateShouldRunTimer(false)
             
             UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
                 view.transform = CGAffineTransform(translationX: 0, y: translation.y)
@@ -306,13 +406,13 @@ struct StoryCoreView: View, AmityViewIdentifiable {
             
         }, onDragEnded: { direction, translation in
             guard let view = host.controller?.view else { return }
+            guard direction == .downward || direction == .upward else { return }
             
-            storyPageViewModel.shouldRunTimer = true
-            storyCoreViewModel.playVideo = true
+            storyPageViewModel.debounceUpdateShouldRunTimer(true)
             
             if translation.y <= 200 {
                 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
-                    if view.transform == .identity && direction == .upward {
+                    if view.transform == .identity && direction == .upward  && translation.y < -100 {
                         showCommentTray = true
                     }
                     
@@ -321,7 +421,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
             } else {
                 host.controller?.dismiss(animated: true)
             }
-    })
+        })
     }
     
     
@@ -401,9 +501,9 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                 
                 Task {
                     if story.isLiked {
-                        try await storyPageViewModel.removeReaction(storyId: story.storyId)
+                        try await viewModel.removeReaction(storyId: story.storyId)
                     } else {
-                        try await storyPageViewModel.addReaction(storyId: story.storyId)
+                        try await viewModel.addReaction(storyId: story.storyId)
                     }
                 }
             }
@@ -432,7 +532,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                 let retryAction = UIAlertAction(title: AmityLocalizedStringSet.General.retry.localizedString, style: .default, handler: {_ in
                     Task {
                         do {
-                            try await storyCoreViewModel.storyManager.deleteStory(storyId: storyModel.storyId)
+                            try await viewModel.storyManager.deleteStory(storyId: storyModel.storyId)
                             
                             switch storyModel.storyType {
                             case .image:
@@ -440,7 +540,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                                     let targetType: AmityStoryTargetType = AmityStoryTargetType(rawValue: storyTarget.targetType) ?? .community
                                     let createOptions = AmityImageStoryCreateOptions(targetType: targetType, tartgetId: storyTarget.targetId, imageFileURL: url, items: storyModel.storyItems)
                                     
-                                    try await storyCoreViewModel.storyManager.createImageStory(in: storyTarget.targetId, createOption: createOptions)
+                                    try await viewModel.storyManager.createImageStory(in: storyTarget.targetId, createOption: createOptions)
                                 }
                                 
                             case .video:
@@ -448,7 +548,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                                     let targetType: AmityStoryTargetType = AmityStoryTargetType(rawValue: storyTarget.targetType) ?? .community
                                     let createOptions = AmityVideoStoryCreateOptions(targetType: targetType, tartgetId: storyTarget.targetId, videoFileURL: url, items: storyModel.storyItems)
                                     
-                                    try await storyCoreViewModel.storyManager.createVideoStory(in: storyTarget.targetId, createOption: createOptions)
+                                    try await viewModel.storyManager.createVideoStory(in: storyTarget.targetId, createOption: createOptions)
                                 }
                                 
                             }
@@ -468,7 +568,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                     
                     let deleteAction = UIAlertAction(title: AmityLocalizedStringSet.General.delete.localizedString, style: .destructive) { _ in
                         Task {
-                            try await storyCoreViewModel.storyManager.deleteStory(storyId: storyModel.storyId)
+                            try await viewModel.storyManager.deleteStory(storyId: storyModel.storyId)
                         }
                         showRetryAlert.toggle()
                     }
@@ -527,7 +627,7 @@ struct StoryCoreView: View, AmityViewIdentifiable {
     
     @ViewBuilder
     func getCommentSheetContentView() -> some View {
-        if let item = storyTarget.items.element(at: storySegmentIndex),
+        if let item = storyTarget.items.element(at: viewModel.storySegmentIndex),
            case let .content(story) = item.type {
             let isCommunityMember = story.storyTarget?.community?.isJoined ?? true
             let allowCreateComment = story.storyTarget?.community?.storySettings.allowComment ?? false
@@ -539,114 +639,57 @@ struct StoryCoreView: View, AmityViewIdentifiable {
                                       shouldAllowCreation: allowCreateComment)
         }
     }
-}
-
-
-class StoryCoreViewModel: ObservableObject {
-    
-    @Published var playVideo: Bool = true
-    let storyManager = StoryManager()
-    
-    init() {}
-}
-
-struct StoryImageView: View {
-    
-    @EnvironmentObject var storyPageViewModel: AmityStoryPageViewModel
-    
-    private let imageURL: URL
-    private let displayMode: ContentMode
-    private let size: CGSize
-    @Binding private var totalDuration: CGFloat
     
-    init(imageURL: URL, totalDuration: Binding<CGFloat>, displayMode: ContentMode, size: CGSize) {
-        self.imageURL = imageURL
-        self._totalDuration = totalDuration
-        self.displayMode = displayMode
-        self.size = size
-    }
     
-    var body: some View {
-        URLImage(imageURL) { progress in
-            ProgressView().progressViewStyle(CircularProgressViewStyle(tint: .white))
-                .onAppear {
-                    storyPageViewModel.shouldRunTimer = false
-                }
-            
-        } content: { image, imageInfo in
-            image
-                .resizable()
-                .aspectRatio(contentMode: displayMode)
-                .frame(width: size.width, height: size.height)
-                .background(
-                    LinearGradient(
-                        gradient: Gradient(colors: UIImage(cgImage: imageInfo.cgImage).averageGradientColor ?? [.black]),
-                        startPoint: .topLeading,
-                        endPoint: .bottomTrailing
-                    )
-                )
-                .onAppear {
-                    storyPageViewModel.shouldRunTimer = true
+    @ViewBuilder
+    private func getBottomSheetView() -> some View {
+        VStack(spacing: 0) {
+            BottomSheetItemView(icon: AmityIcon.trashBinIcon.getImageResource(), text: "Delete story", isDestructive: true)
+                .onTapGesture {
+                    let alertController = UIAlertController(title: AmityLocalizedStringSet.Story.deleteStoryTitle.localizedString, message: AmityLocalizedStringSet.Story.deleteStoryMessage.localizedString, preferredStyle: .alert)
+                    
+                    let cancelAction = UIAlertAction(title: AmityLocalizedStringSet.General.cancel.localizedString, style: .cancel) { _ in
+                        isAlertShown.toggle()
+                    }
+                    
+                    let deleteAction = UIAlertAction(title: AmityLocalizedStringSet.General.delete.localizedString, style: .destructive) { _ in
+                        isAlertShown.toggle()
+                        if let item = storyTarget.items.element(at: viewModel.storySegmentIndex),
+                            case let .content(story) = item.type {
+                            Task { @MainActor in
+                                try await viewModel.deleteStory(storyId: story.storyId, host)
+                                Toast.showToast(style: .success, message: "Successfully deleted the story.")
+                            }
+                        }
+                    }
+                    
+                    alertController.addAction(cancelAction)
+                    alertController.addAction(deleteAction)
+                    
+                    showBottomSheet.toggle()
+                    isAlertShown.toggle()
+                    
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                        host.controller?.present(alertController, animated: true)
+                    }
                 }
-                .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.storyImageView)
-        }
-        .environment(\.urlImageOptions, URLImageOptions.amityOptions)
-        .onAppear {
-            totalDuration = STORY_DURATION
-            Log.add(event: .info, "Story TotalDuration: \(totalDuration)")
         }
+        .padding(.bottom, 32)
     }
-}
-
-struct StoryVideoView: View {
-    
-    @EnvironmentObject var storyPageViewModel: AmityStoryPageViewModel
-    @EnvironmentObject var storyCoreViewModel: StoryCoreViewModel
-    
-    private let videoURL: URL
-    @Binding var totalDuration: CGFloat
-    @Binding var muteVideo: Bool
-    @Binding var playVideo: Bool
-    
-    @State private var showActivityIndicator: Bool = false
-    @State private var time: CMTime = .zero
     
-    init(videoURL: URL, totalDuration: Binding<CGFloat>, muteVideo: Binding<Bool>, playVideo: Binding<Bool>) {
-        self.videoURL = videoURL
-        self._totalDuration = totalDuration
-        self._muteVideo = muteVideo
-        self._playVideo = playVideo
-    }
-    
-    var body: some View {
-        VideoPlayer(url: videoURL, play: $playVideo, time: $time)
-            .autoReplay(false)
-            .mute(muteVideo)
-            .contentMode(.scaleAspectFit)
-            .onStateChanged({ state in
-                switch state {
-                case .loading:
-                    storyPageViewModel.shouldRunTimer = false
-                    showActivityIndicator = true
-                case .playing(totalDuration: let totalDuration):
-                    storyPageViewModel.shouldRunTimer = true
-                    self.totalDuration = totalDuration
-                    showActivityIndicator = false
-                    Log.add(event: .info, "Story TotalDuration: \(totalDuration)")
-                case .paused(playProgress: _, bufferProgress: _): break
-                case .error(_): break
-                    
-                }
-            })
-            .overlay(
-                ActivityIndicatorView(isAnimating: $showActivityIndicator, style: .medium)
-            )
-            .onAppear {
-                time = .zero
-                storyPageViewModel.shouldRunTimer = true
-            }
-            .onDisappear {
+    private func checkStoryPermission() {
+        // Check StoryManage Permission
+        Task {
+            let storyTargetId = storyTarget.targetId
+            let hasPermission = await StoryPermissionChecker.checkUserHasManagePermission(communityId: storyTargetId)
+            let allowAllUserCreation = AmityUIKitManagerInternal.shared.client.getSocialSettings()?.story?.allowAllUserToCreateStory ?? false
+            
+            guard let community = storyTarget.storyTarget?.community else {
+                hasStoryManagePermission = false
+                return
             }
-            .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.storyVideoView)
+            
+            hasStoryManagePermission = (allowAllUserCreation || hasPermission) && community.isJoined
+        }
     }
 }
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreViewModel.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreViewModel.swift
new file mode 100644
index 0000000..dc369c0
--- /dev/null
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryCoreViewModel.swift
@@ -0,0 +1,190 @@
+//
+//  StoryCoreViewModel.swift
+//  AmityUIKit4
+//
+//  Created by Zay Yar Htun on 9/3/24.
+//
+
+import SwiftUI
+import AVKit
+import Combine
+import AmitySDK
+
+enum MoveDirection {
+    case forward, backward
+}
+
+class StoryCoreViewModel: ObservableObject, Equatable {
+    
+    static func == (lhs: StoryCoreViewModel, rhs: StoryCoreViewModel) -> Bool {
+        lhs.storyTarget.targetId == rhs.storyTarget.targetId
+    }
+    
+    @Published var storySegmentIndex: Int = 0
+    @Published var playVideo: Bool = false
+    @Published var playTime: CMTime = .zero
+    @Published var isVideoLoading: Bool = false
+    @Published var progressBarViewModel = ProgressBarViewModel(progressArray: [])
+    
+    weak var storyPageViewModel: AmityViewStoryPageViewModel!
+    weak var storyTarget: AmityStoryTargetModel!
+   
+    let storyManager = StoryManager()
+    private var cancellable: AnyCancellable?
+    private let debouncer = Debouncer(delay: 0.01)
+    
+    init(_ storyTarget: AmityStoryTargetModel, storyPageViewModel: AmityViewStoryPageViewModel) {
+        self.storyTarget = storyTarget
+        self.storyPageViewModel = storyPageViewModel
+    }
+    
+    func playPauseVideo(_ play: Bool) {
+        if let item = storyTarget.items.element(at: storySegmentIndex),
+           case let .content(storyModel) = item.type,
+           case .video = storyModel.storyType {
+            playVideo = play
+        }
+    }
+    
+    func stopVideo() {
+        playVideo = false
+        playTime = .zero
+    }
+        
+    func playIfVideoStory() {
+        if let item = storyTarget.items.element(at: storySegmentIndex),
+           case let .content(storyModel) = item.type,
+           case .video = storyModel.storyType {
+            playTime = .zero
+            playVideo = true
+        } else {
+            playVideo = false
+            playTime = .zero
+            storyPageViewModel.totalDuration = STORY_DURATION
+        }
+    }
+    
+    func isLoadedIfVideoStory() -> Bool {
+        if let item = storyTarget.items.element(at: storySegmentIndex),
+           case let .content(storyModel) = item.type,
+           case .video = storyModel.storyType {
+            return !isVideoLoading
+        } else {
+            return true
+        }
+    }
+    
+    func moveStorySegment(direction: MoveDirection, _ host: AmitySwiftUIHostWrapper) {
+        guard 0..<progressBarViewModel.progressArray.count ~= storySegmentIndex else {
+            return
+        }
+
+        switch direction {
+        case .forward:
+            progressBarViewModel.progressArray[storySegmentIndex].progress = progressBarViewModel.segmentFullProgress
+            
+            let lastSegmentIndex = progressBarViewModel.progressArray.count - 1
+            let lastTargetIndex = storyPageViewModel.storyTargets.count - 1
+            
+            if storySegmentIndex != lastSegmentIndex {
+                storySegmentIndex += 1
+            } else if storySegmentIndex == lastSegmentIndex && storyPageViewModel.storyTargetIndex != lastTargetIndex {
+                storySegmentIndex = 0
+                storyPageViewModel.storyTargetIndex += 1
+            }
+            else if storyPageViewModel.storyTargetIndex == lastTargetIndex {
+                host.controller?.dismiss(animated: true)
+            }
+        
+        case .backward:
+            progressBarViewModel.progressArray[storySegmentIndex].progress = 0
+            
+            if storySegmentIndex >= 1 {
+                storySegmentIndex -= 1
+                progressBarViewModel.progressArray[storySegmentIndex].progress  = 0
+            } else if storyPageViewModel.storyTargetIndex >= 1 {
+                updateProgressBarViewModelProgressArray(0)
+                storyPageViewModel.storyTargetIndex -= 1
+            }
+        }
+    }
+    
+    func timerAction(_ host: AmitySwiftUIHostWrapper) {
+        guard 0..<progressBarViewModel.progressArray.count ~= storySegmentIndex else {
+            return
+        }
+        
+        let lastSegmentIndex = progressBarViewModel.progressArray.count - 1
+        let lastTargetIndex = storyPageViewModel.storyTargets.count - 1
+        
+        if progressBarViewModel.progressArray[storySegmentIndex].progress == progressBarViewModel.segmentFullProgress {
+            if storySegmentIndex != lastSegmentIndex {
+                storySegmentIndex += 1
+            } else if storyPageViewModel.storyTargetIndex != lastTargetIndex {
+                storySegmentIndex = 0
+                updateProgressBarViewModelProgressArray(0)
+                storyPageViewModel.storyTargetIndex += 1
+            } else if storyPageViewModel.storyTargetIndex == lastTargetIndex {
+                host.controller?.dismiss(animated: true)
+            }
+        } else {
+            let progressValueToIncrease = progressBarViewModel.segmentFullProgress / CGFloat(storyPageViewModel.totalDuration / 0.001)
+            let oldProgress = progressBarViewModel.progressArray[storySegmentIndex].progress
+            let newProgress = oldProgress + progressValueToIncrease
+            progressBarViewModel.progressArray[storySegmentIndex].progress = min(max(oldProgress, newProgress), progressBarViewModel.segmentFullProgress)
+        }
+    }
+
+    func createProgressBarViewModelProgressArray(_ segmentCount: Int) {
+        progressBarViewModel.progressArray = (0..<segmentCount).map({ index in
+            return AmityProgressBarElementViewModel()
+        })
+    }
+    
+    func updateProgressBarViewModelProgressArray(_ currentIndex: Int) {
+        progressBarViewModel.progressArray.enumerated()
+            .forEach { index, model in
+                if index < currentIndex {
+                    model.progress = progressBarViewModel.segmentFullProgress
+                } else {
+                    model.progress = 0.0
+                }
+            }
+    }
+    
+    func moveToUnseenStory(_ index: Int) {
+        debouncer.run { [weak self] in
+            guard self?.storySegmentIndex != index else { return }
+            self?.updateProgressBarViewModelProgressArray(index)
+            self?.storySegmentIndex = index
+        }
+    }
+    
+    @MainActor
+    func deleteStory(storyId: String, _ host: AmitySwiftUIHostWrapper) async throws {
+        let isLastStory = storyTarget.itemCount == 1
+        let isLastTarget = storyPageViewModel.storyTargets.count == 1
+        try await storyManager.deleteStory(storyId: storyId)
+        
+        if isLastStory && !isLastTarget {
+            storyPageViewModel.storyTargets.remove(at: storyPageViewModel.storyTargetIndex)
+        } else if isLastStory && isLastTarget {
+            host.controller?.dismiss(animated: true)
+        }
+        
+        moveStorySegment(direction: .backward, host)
+    }
+    
+    @MainActor
+    @discardableResult
+    func addReaction(storyId: String) async throws -> Bool {
+        try await storyManager.addReaction(storyId: storyId)
+    }
+    
+    @MainActor
+    @discardableResult
+    func removeReaction(storyId: String) async throws -> Bool {
+        try await storyManager.removeReaction(storyId: storyId)
+    }
+    
+}
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryImageView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryImageView.swift
new file mode 100644
index 0000000..cd206b3
--- /dev/null
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryImageView.swift
@@ -0,0 +1,51 @@
+//
+//  StoryImageView.swift
+//  AmityUIKit4
+//
+//  Created by Zay Yar Htun on 9/3/24.
+//
+
+import SwiftUI
+
+struct StoryImageView: View {
+    private let imageURL: URL
+    private let displayMode: ContentMode
+    private let size: CGSize
+    private let onLoading: () -> Void
+    private let onLoaded: () -> Void
+    
+    init(imageURL: URL, displayMode: ContentMode, size: CGSize, onLoading: @escaping () -> Void, onLoaded: @escaping () -> Void) {
+        self.imageURL = imageURL
+        self.displayMode = displayMode
+        self.size = size
+        self.onLoading = onLoading
+        self.onLoaded = onLoaded
+    }
+    
+    var body: some View {
+        URLImage(imageURL) { progress in
+            Color.clear
+                .onAppear {
+                    onLoading()
+                }
+            
+        } content: { image, imageInfo in
+            image
+                .resizable()
+                .aspectRatio(contentMode: displayMode)
+                .frame(width: size.width, height: size.height)
+                .background(
+                    LinearGradient(
+                        gradient: Gradient(colors: UIImage(cgImage: imageInfo.cgImage).averageGradientColor ?? [.black]),
+                        startPoint: .topLeading,
+                        endPoint: .bottomTrailing
+                    )
+                )
+                .onAppear {
+                    onLoaded()
+                }
+                .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.storyImageView)
+        }
+        .environment(\.urlImageOptions, URLImageOptions.amityOptions)
+    }
+}
diff --git a/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryVideoView.swift b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryVideoView.swift
new file mode 100644
index 0000000..06ab786
--- /dev/null
+++ b/UpstraUIKit/AmityUIKit4/AmityUIKit4/Story/Pages/ViewStory/ChildViews/StoryVideoView.swift
@@ -0,0 +1,47 @@
+//
+//  StoryVideoView.swift
+//  AmityUIKit4
+//
+//  Created by Zay Yar Htun on 9/3/24.
+//
+
+import SwiftUI
+import AVKit
+
+struct StoryVideoView: View {
+        
+    private let videoURL: URL
+    @Binding private var muteVideo: Bool
+    @Binding private var playVideo: Bool
+    @Binding private var time: CMTime
+    private let onLoading: () -> Void
+    private let onPlaying: (Double) -> Void
+    
+    init(videoURL: URL, muteVideo: Binding<Bool>, time: Binding<CMTime>, playVideo: Binding<Bool>, onLoading: @escaping () -> Void, onPlaying: @escaping (Double) -> Void) {
+        self.videoURL = videoURL
+        self._muteVideo = muteVideo
+        self._time = time
+        self._playVideo = playVideo
+        self.onLoading = onLoading
+        self.onPlaying = onPlaying
+    }
+    
+    var body: some View {
+        VideoPlayer(url: videoURL, play: $playVideo, time: $time)
+            .autoReplay(false)
+            .mute(muteVideo)
+            .contentMode(.scaleAspectFit)
+            .onStateChanged({ state in
+                switch state {
+                case .loading:
+                    onLoading()
+                case .playing(totalDuration: let totalDuration):
+                    onPlaying(totalDuration)
+                case .paused(playProgress: _, bufferProgress: _): break
+                case .error(_): break
+                }
+            })
+            .accessibilityIdentifier(AccessibilityID.Story.AmityViewStoryPage.storyVideoView)
+    }
+}
+
diff --git a/UpstraUIKit/AmityUIKitLiveStream/AmityUIKitLiveStream.xcodeproj/project.pbxproj b/UpstraUIKit/AmityUIKitLiveStream/AmityUIKitLiveStream.xcodeproj/project.pbxproj
index 9d30013..dd1be74 100644
--- a/UpstraUIKit/AmityUIKitLiveStream/AmityUIKitLiveStream.xcodeproj/project.pbxproj
+++ b/UpstraUIKit/AmityUIKitLiveStream/AmityUIKitLiveStream.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		682E021A2CF0AE6900FE1042 /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = 682E02192CF0AE6900FE1042 /* SharedFrameworks */; };
 		92952FDF2CC0E78E0012B2B9 /* LivestreamPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92952FDE2CC0E78E0012B2B9 /* LivestreamPlayerView.swift */; };
 		92952FE52CC5B1F90012B2B9 /* RecordedStreamPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92952FE42CC5B1F90012B2B9 /* RecordedStreamPlayerView.swift */; };
 		A0B68B3026E07278007D7B5B /* LiveStreamViewController+GoLive.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0B68B2F26E07278007D7B5B /* LiveStreamViewController+GoLive.swift */; };
@@ -31,7 +32,6 @@
 		A0BD0B5E26DF37160054088B /* LiveStreamBroadcastVC+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0BD0B5D26DF37160054088B /* LiveStreamBroadcastVC+Keyboard.swift */; };
 		A0BD0B6026DF377A0054088B /* LiveStreamBroadcastVC+CoverImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0BD0B5F26DF377A0054088B /* LiveStreamBroadcastVC+CoverImagePicker.swift */; };
 		A0BD0B6226DF3A3F0054088B /* LiveStreamBroadcast+UIContainerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0BD0B6126DF3A3F0054088B /* LiveStreamBroadcast+UIContainerState.swift */; };
-		ED52321B2CDE391700ABA50D /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = ED52321A2CDE391700ABA50D /* SharedFrameworks */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -73,7 +73,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				ED52321B2CDE391700ABA50D /* SharedFrameworks in Frameworks */,
+				682E021A2CF0AE6900FE1042 /* SharedFrameworks in Frameworks */,
 				A0BD0B3426DDD9820054088B /* AmityUIKit.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -251,7 +251,7 @@
 			);
 			name = AmityUIKitLiveStream;
 			packageProductDependencies = (
-				ED52321A2CDE391700ABA50D /* SharedFrameworks */,
+				682E02192CF0AE6900FE1042 /* SharedFrameworks */,
 			);
 			productName = AmityUIKitLiveStream;
 			productReference = A0BD0B1526DCE4F50054088B /* AmityUIKitLiveStream.framework */;
@@ -537,7 +537,7 @@
 /* End XCConfigurationList section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		ED52321A2CDE391700ABA50D /* SharedFrameworks */ = {
+		682E02192CF0AE6900FE1042 /* SharedFrameworks */ = {
 			isa = XCSwiftPackageProductDependency;
 			productName = SharedFrameworks;
 		};
diff --git a/UpstraUIKit/SampleApp/SampleApp.xcodeproj/project.pbxproj b/UpstraUIKit/SampleApp/SampleApp.xcodeproj/project.pbxproj
index 73adbcd..0239349 100644
--- a/UpstraUIKit/SampleApp/SampleApp.xcodeproj/project.pbxproj
+++ b/UpstraUIKit/SampleApp/SampleApp.xcodeproj/project.pbxproj
@@ -22,6 +22,7 @@
 		680DC0042C8AC2B2006D8764 /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680DC0022C8ABE09006D8764 /* UIDevice+Extension.swift */; };
 		682C761C2B331DAE00018F80 /* AmityUIKit4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 684AE12D2B0C841400FD7270 /* AmityUIKit4.framework */; };
 		682C761D2B331DAE00018F80 /* AmityUIKit4.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 684AE12D2B0C841400FD7270 /* AmityUIKit4.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		682E021C2CF0AE8100FE1042 /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = 682E021B2CF0AE8100FE1042 /* SharedFrameworks */; };
 		684097562B30607F00697E1B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 684097552B30607F00697E1B /* GoogleService-Info.plist */; };
 		68F5D9FE2B481E4700A9FA0D /* AmityUIKit4.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68F5D9FD2B481E4700A9FA0D /* AmityUIKit4.framework */; };
 		68F5D9FF2B481E4700A9FA0D /* AmityUIKit4.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 68F5D9FD2B481E4700A9FA0D /* AmityUIKit4.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -98,6 +99,8 @@
 		92DBE8E52ACA98CF007D873C /* AmityUIKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D478D153262409E5006EA140 /* AmityUIKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		97F101F526A69FBF00AD84A1 /* CustomChannelEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F101F426A69FBF00AD84A1 /* CustomChannelEventHandler.swift */; };
 		A03190A7272169C1008A85DC /* PostCreatorSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A03190A6272169C1008A85DC /* PostCreatorSettingsPage.swift */; };
+		A0BD0B4826DDE0E30054088B /* AmityUIKitLiveStream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0BD0B4726DDE0E30054088B /* AmityUIKitLiveStream.framework */; };
+		A0BD0B4926DDE0E30054088B /* AmityUIKitLiveStream.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A0BD0B4726DDE0E30054088B /* AmityUIKitLiveStream.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		A9FF80E62BBD10010088A317 /* LiveChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FF80E52BBD10010088A317 /* LiveChatListView.swift */; };
 		A9FF80ED2BBD55870088A317 /* LiveChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FF80E52BBD10010088A317 /* LiveChatListView.swift */; };
 		B72861D924C573B100ECC563 /* TabbarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B72861D824C573B100ECC563 /* TabbarViewController.swift */; };
@@ -125,7 +128,6 @@
 		D4AFF1CF25ECE8AD002C001C /* PostPreviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AFF1CD25ECE8AD002C001C /* PostPreviewTableViewCell.swift */; };
 		D4AFF23225ED1E7C002C001C /* GlobalPostsFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AFF23125ED1E7C002C001C /* GlobalPostsFeedViewController.swift */; };
 		D4AFF23625ED1F88002C001C /* GlobalPostsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AFF23525ED1F88002C001C /* GlobalPostsDataSource.swift */; };
-		ED52321D2CDE392A00ABA50D /* SharedFrameworks in Frameworks */ = {isa = PBXBuildFile; productRef = ED52321C2CDE392A00ABA50D /* SharedFrameworks */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -171,6 +173,7 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
+				A0BD0B4926DDE0E30054088B /* AmityUIKitLiveStream.framework in Embed Frameworks */,
 				68F5D9FF2B481E4700A9FA0D /* AmityUIKit4.framework in Embed Frameworks */,
 				D478D16A26240A5E006EA140 /* AmityUIKit.framework in Embed Frameworks */,
 			);
@@ -276,9 +279,10 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				682E021C2CF0AE8100FE1042 /* SharedFrameworks in Frameworks */,
+				A0BD0B4826DDE0E30054088B /* AmityUIKitLiveStream.framework in Frameworks */,
 				68F5D9FE2B481E4700A9FA0D /* AmityUIKit4.framework in Frameworks */,
 				D478D16926240A5E006EA140 /* AmityUIKit.framework in Frameworks */,
-				ED52321D2CDE392A00ABA50D /* SharedFrameworks in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -601,7 +605,7 @@
 			);
 			name = SampleApp;
 			packageProductDependencies = (
-				ED52321C2CDE392A00ABA50D /* SharedFrameworks */,
+				682E021B2CF0AE8100FE1042 /* SharedFrameworks */,
 			);
 			productName = SampleApp;
 			productReference = B78DA47524BED7D300EE902B /* SampleApp.app */;
@@ -1284,15 +1288,15 @@
 /* End XCRemoteSwiftPackageReference section */
 
 /* Begin XCSwiftPackageProductDependency section */
+		682E021B2CF0AE8100FE1042 /* SharedFrameworks */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = SharedFrameworks;
+		};
 		92DBE8A32ACA98CF007D873C /* FirebaseCrashlytics */ = {
 			isa = XCSwiftPackageProductDependency;
 			package = 92DBE8A42ACA98CF007D873C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
 			productName = FirebaseCrashlytics;
 		};
-		ED52321C2CDE392A00ABA50D /* SharedFrameworks */ = {
-			isa = XCSwiftPackageProductDependency;
-			productName = SharedFrameworks;
-		};
 /* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = B78DA46D24BED7D300EE902B /* Project object */;
diff --git a/UpstraUIKit/SharedFrameworks/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/UpstraUIKit/SharedFrameworks/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 94b2795..0000000
--- a/UpstraUIKit/SharedFrameworks/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-</Workspace>
diff --git a/UpstraUIKit/SharedFrameworks/Package.swift b/UpstraUIKit/SharedFrameworks/Package.swift
index ddb499f..765de48 100644
--- a/UpstraUIKit/SharedFrameworks/Package.swift
+++ b/UpstraUIKit/SharedFrameworks/Package.swift
@@ -23,28 +23,28 @@ let package = Package(
             dependencies: []),
         .binaryTarget(
                     name: "AmitySDK",
-                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta28/AmitySDK.xcframework.zip",
-                    checksum: "8efe8405ac20d5c9278cb51c46dae5f949806598de1a51e4f9d9f6358a9ac8f5"
+                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta29/AmitySDK.xcframework.zip",
+                    checksum: "59e9996a707e3ba630e53265157795b4f486b382ccfbcd833fd3cb2574310051"
                 ),
         .binaryTarget(
                     name: "Realm",
-                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta28/Realm.xcframework.zip",
-                    checksum: "3b49824e12e89f6a5ff13374d1f31e45b5c7748a32f7b8148a14ba84c372b586"
+                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta29/Realm.xcframework.zip",
+                    checksum: "8c2b1e560b094f2d538f8a1ae59515a0137f34aade896bb81dca797633828c14"
                 ),
          .binaryTarget(
                     name: "RealmSwift",
-                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta28/RealmSwift.xcframework.zip",
-                    checksum: "b14bb4032f9240dad52034f10f9796adaee4f258b53af7c9bd9e68036a392ed2"
+                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta29/RealmSwift.xcframework.zip",
+                    checksum: "19eccbecdbed93e800539fd8f45cc1526fe9642440728b87990a8c0e4cbd2517"
                 ),
         .binaryTarget(
                     name: "AmityLiveVideoBroadcastKit",
-                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta28/AmityLiveVideoBroadcastKit.xcframework.zip",
-                    checksum: "da25546df19250e3ad31092ca7049b7b16b59da087346b264c1a7fb2c6a25352"
+                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta29/AmityLiveVideoBroadcastKit.xcframework.zip",
+                    checksum: "623717aa399da68759432c9e27d0cfc31374f20d90cd365252ce2f09e02fa784"
                 ),
         .binaryTarget(
                     name: "AmityVideoPlayerKit",
-                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta28/AmityVideoPlayerKit.xcframework.zip",
-                    checksum: "f3e45aa5657504c0fc017b47edafa125f60ea738d735ddeaac50941d3b4417bb"
+                    url: "https://sdk.amity.co/sdk-release/ios-uikit-frameworks/4.0.0-beta29/AmityVideoPlayerKit.xcframework.zip",
+                    checksum: "94aadec8a3aeae3190d5ebab3644fa460bde87a863bcea1ab3dd62e12064695a"
                 ),
         .binaryTarget(
                     name: "MobileVLCKit",
diff --git a/UpstraUIKit/UpstraUIKit/Components/AmityExpandableLabel.swift b/UpstraUIKit/UpstraUIKit/Components/AmityExpandableLabel.swift
index 4103ecf..031a357 100644
--- a/UpstraUIKit/UpstraUIKit/Components/AmityExpandableLabel.swift
+++ b/UpstraUIKit/UpstraUIKit/Components/AmityExpandableLabel.swift
@@ -156,8 +156,8 @@ open class AmityExpandableLabel: UILabel {
         set(text) {
             if let text = text {
                 let attributedString = NSMutableAttributedString(string: text)
-                let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
-                let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
+                let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
+                let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) ?? []
                 var _hyperLinkTextRange: [Hyperlink] = []
                 for match in matches {
                     guard let textRange = Range(match.range, in: text) else { continue }
@@ -606,8 +606,8 @@ extension AmityExpandableLabel {
     
     func setText(_ text: String, withAttributes attributes: [MentionAttribute]) {
         let attributedString = NSMutableAttributedString(string: text)
-        let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
-        let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
+        let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
+        let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) ?? []
         var _hyperLinkTextRange: [Hyperlink] = []
         for match in matches {
             guard let textRange = Range(match.range, in: text) else { continue }