react-native-photo-video-editor 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/PhotoVideoEditor.podspec +1 -0
- package/README.md +10 -6
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/assets/Stickers/AUTUMN_1.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_10.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_11.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_12.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_13.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_14.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_15.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_16.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_17.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_18.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_2.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_3.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_4.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_5.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_6.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_7.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_8.png +0 -0
- package/android/src/main/assets/Stickers/AUTUMN_9.png +0 -0
- package/android/src/main/assets/Stickers/LOL_1.png +0 -0
- package/android/src/main/assets/Stickers/LOL_10.png +0 -0
- package/android/src/main/assets/Stickers/LOL_11.png +0 -0
- package/android/src/main/assets/Stickers/LOL_12.png +0 -0
- package/android/src/main/assets/Stickers/LOL_13.png +0 -0
- package/android/src/main/assets/Stickers/LOL_14.png +0 -0
- package/android/src/main/assets/Stickers/LOL_15.png +0 -0
- package/android/src/main/assets/Stickers/LOL_16.png +0 -0
- package/android/src/main/assets/Stickers/LOL_17.png +0 -0
- package/android/src/main/assets/Stickers/LOL_18.png +0 -0
- package/android/src/main/assets/Stickers/LOL_19.png +0 -0
- package/android/src/main/assets/Stickers/LOL_2.png +0 -0
- package/android/src/main/assets/Stickers/LOL_3.png +0 -0
- package/android/src/main/assets/Stickers/LOL_4.png +0 -0
- package/android/src/main/assets/Stickers/LOL_5.png +0 -0
- package/android/src/main/assets/Stickers/LOL_6.png +0 -0
- package/android/src/main/assets/Stickers/LOL_7.png +0 -0
- package/android/src/main/assets/Stickers/LOL_8.png +0 -0
- package/android/src/main/assets/Stickers/LOL_9.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_1.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_2.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_3.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_4.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_5.png +0 -0
- package/android/src/main/assets/Stickers/SPRING_6.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_1.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_10.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_11.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_12.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_13.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_14.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_15.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_16.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_17.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_18.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_19.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_2.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_20.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_21.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_3.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_4.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_5.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_6.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_7.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_8.png +0 -0
- package/android/src/main/assets/Stickers/SUMMER_9.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_1.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_12.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_13.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_14.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_15.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_16.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_2.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_25.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_26.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_27.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_28.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_29.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_3.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_30.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_31.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_32.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_7.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_8.png +0 -0
- package/android/src/main/assets/Stickers/TEXT_9.png +0 -0
- package/android/src/main/java/com/photovideoeditor/editor/PhotoVideoEditorActivity.kt +188 -108
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayer.kt +5 -1
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayerRenderer.kt +21 -0
- package/android/src/main/java/com/photovideoeditor/photo/ui/CropOverlayView.kt +34 -2
- package/android/src/main/java/com/photovideoeditor/photo/ui/DrawOverlayView.kt +66 -0
- package/android/src/main/java/com/photovideoeditor/photo/ui/LayerOverlayView.kt +21 -1
- package/android/src/main/java/com/photovideoeditor/stickers/OnlineStickerSheet.kt +135 -104
- package/android/src/main/res/drawable/ic_draw.xml +6 -0
- package/android/src/main/res/drawable/ic_pause.xml +7 -0
- package/android/src/main/res/drawable/ic_play.xml +7 -0
- package/docs/api.md +1 -1
- package/docs/photo-editor.md +5 -6
- package/ios/Editor/PhotoVideoEditorViewController.swift +166 -77
- package/ios/Photo/Rendering/PhotoLayer.swift +5 -1
- package/ios/Photo/Rendering/PhotoLayerRenderer.swift +14 -0
- package/ios/Photo/UI/CropOverlayView.swift +38 -1
- package/ios/Photo/UI/DrawOverlayView.swift +48 -0
- package/ios/Photo/UI/LayerOverlayView.swift +9 -1
- package/ios/Resources/Stickers/AUTUMN_1.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_10.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_11.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_12.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_13.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_14.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_15.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_16.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_17.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_18.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_2.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_3.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_4.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_5.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_6.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_7.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_8.png +0 -0
- package/ios/Resources/Stickers/AUTUMN_9.png +0 -0
- package/ios/Resources/Stickers/LOL_1.png +0 -0
- package/ios/Resources/Stickers/LOL_10.png +0 -0
- package/ios/Resources/Stickers/LOL_11.png +0 -0
- package/ios/Resources/Stickers/LOL_12.png +0 -0
- package/ios/Resources/Stickers/LOL_13.png +0 -0
- package/ios/Resources/Stickers/LOL_14.png +0 -0
- package/ios/Resources/Stickers/LOL_15.png +0 -0
- package/ios/Resources/Stickers/LOL_16.png +0 -0
- package/ios/Resources/Stickers/LOL_17.png +0 -0
- package/ios/Resources/Stickers/LOL_18.png +0 -0
- package/ios/Resources/Stickers/LOL_19.png +0 -0
- package/ios/Resources/Stickers/LOL_2.png +0 -0
- package/ios/Resources/Stickers/LOL_3.png +0 -0
- package/ios/Resources/Stickers/LOL_4.png +0 -0
- package/ios/Resources/Stickers/LOL_5.png +0 -0
- package/ios/Resources/Stickers/LOL_6.png +0 -0
- package/ios/Resources/Stickers/LOL_7.png +0 -0
- package/ios/Resources/Stickers/LOL_8.png +0 -0
- package/ios/Resources/Stickers/LOL_9.png +0 -0
- package/ios/Resources/Stickers/SPRING_1.png +0 -0
- package/ios/Resources/Stickers/SPRING_2.png +0 -0
- package/ios/Resources/Stickers/SPRING_3.png +0 -0
- package/ios/Resources/Stickers/SPRING_4.png +0 -0
- package/ios/Resources/Stickers/SPRING_5.png +0 -0
- package/ios/Resources/Stickers/SPRING_6.png +0 -0
- package/ios/Resources/Stickers/SUMMER_1.png +0 -0
- package/ios/Resources/Stickers/SUMMER_10.png +0 -0
- package/ios/Resources/Stickers/SUMMER_11.png +0 -0
- package/ios/Resources/Stickers/SUMMER_12.png +0 -0
- package/ios/Resources/Stickers/SUMMER_13.png +0 -0
- package/ios/Resources/Stickers/SUMMER_14.png +0 -0
- package/ios/Resources/Stickers/SUMMER_15.png +0 -0
- package/ios/Resources/Stickers/SUMMER_16.png +0 -0
- package/ios/Resources/Stickers/SUMMER_17.png +0 -0
- package/ios/Resources/Stickers/SUMMER_18.png +0 -0
- package/ios/Resources/Stickers/SUMMER_19.png +0 -0
- package/ios/Resources/Stickers/SUMMER_2.png +0 -0
- package/ios/Resources/Stickers/SUMMER_20.png +0 -0
- package/ios/Resources/Stickers/SUMMER_21.png +0 -0
- package/ios/Resources/Stickers/SUMMER_3.png +0 -0
- package/ios/Resources/Stickers/SUMMER_4.png +0 -0
- package/ios/Resources/Stickers/SUMMER_5.png +0 -0
- package/ios/Resources/Stickers/SUMMER_6.png +0 -0
- package/ios/Resources/Stickers/SUMMER_7.png +0 -0
- package/ios/Resources/Stickers/SUMMER_8.png +0 -0
- package/ios/Resources/Stickers/SUMMER_9.png +0 -0
- package/ios/Resources/Stickers/TEXT_1.png +0 -0
- package/ios/Resources/Stickers/TEXT_12.png +0 -0
- package/ios/Resources/Stickers/TEXT_13.png +0 -0
- package/ios/Resources/Stickers/TEXT_14.png +0 -0
- package/ios/Resources/Stickers/TEXT_15.png +0 -0
- package/ios/Resources/Stickers/TEXT_16.png +0 -0
- package/ios/Resources/Stickers/TEXT_2.png +0 -0
- package/ios/Resources/Stickers/TEXT_25.png +0 -0
- package/ios/Resources/Stickers/TEXT_26.png +0 -0
- package/ios/Resources/Stickers/TEXT_27.png +0 -0
- package/ios/Resources/Stickers/TEXT_28.png +0 -0
- package/ios/Resources/Stickers/TEXT_29.png +0 -0
- package/ios/Resources/Stickers/TEXT_3.png +0 -0
- package/ios/Resources/Stickers/TEXT_30.png +0 -0
- package/ios/Resources/Stickers/TEXT_31.png +0 -0
- package/ios/Resources/Stickers/TEXT_32.png +0 -0
- package/ios/Resources/Stickers/TEXT_7.png +0 -0
- package/ios/Resources/Stickers/TEXT_8.png +0 -0
- package/ios/Resources/Stickers/TEXT_9.png +0 -0
- package/ios/Resources/Stickers/a_pety_icon.png +0 -0
- package/ios/Stickers/OnlineStickerSheetViewController.swift +102 -159
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +1 -1
- package/src/types.ts +2 -2
- package/android/src/main/java/com/photovideoeditor/stickers/OpenMojiCatalog.kt +0 -172
- package/android/src/main/java/com/photovideoeditor/stickers/RemoteImageLoader.kt +0 -110
- package/ios/Stickers/OpenMojiCatalog.swift +0 -188
- package/ios/Stickers/RemoteImageLoader.swift +0 -127
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,49 +1,42 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
|
|
3
|
-
/// Bottom sheet: search + category filter + a
|
|
4
|
-
///
|
|
3
|
+
/// Bottom sheet: search + category filter + a 5-column grid of bundled stickers. Tapping one
|
|
4
|
+
/// writes the PNG to a temp file and hands `(URL, UIImage)` back to
|
|
5
5
|
/// `onStickerPicked` — the caller feeds that straight into the existing
|
|
6
6
|
/// `handlePickedImage(_:image:purpose:)` insertion path (photo sticker chip / video sticker
|
|
7
7
|
/// action sheet), so this file has zero knowledge of `PhotoLayer`/session internals.
|
|
8
8
|
///
|
|
9
9
|
/// Built entirely in code (no storyboard/xib), consistent with the rest of this codebase.
|
|
10
10
|
final class OnlineStickerSheetViewController: UIViewController {
|
|
11
|
-
|
|
11
|
+
struct RuntimeSticker { let id: String; let uri: String }
|
|
12
|
+
/// Fired once after the bundled image is written to a temp file.
|
|
12
13
|
/// The sheet dismisses itself first, then invokes this closure.
|
|
13
14
|
var onStickerPicked: ((URL, UIImage) -> Void)?
|
|
15
|
+
var onDismiss: (() -> Void)?
|
|
16
|
+
private var didNotifyDismiss = false
|
|
14
17
|
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
case loading, loaded, error(String)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private var loadState: LoadState = .loading
|
|
23
|
-
private var allEntries: [OpenMojiEntry] = []
|
|
24
|
-
private var displayedEntries: [OpenMojiEntry] = []
|
|
18
|
+
private var localStickerNames: [String] = []
|
|
19
|
+
private var displayedLocalStickerNames: [String] = []
|
|
20
|
+
private let runtimeStickers: [RuntimeSticker]
|
|
21
|
+
private var displayedRuntimeStickers: [RuntimeSticker] = []
|
|
25
22
|
private var selectedGroup: String? // nil == "All"
|
|
26
23
|
private var searchQuery: String = ""
|
|
27
24
|
private var searchDebounceTimer: Timer?
|
|
28
|
-
private var downloadInFlightHexcode: String?
|
|
29
25
|
|
|
30
26
|
private let searchBar = UISearchBar()
|
|
31
27
|
private let categoryScroll = UIScrollView()
|
|
32
28
|
private let categoryStack = UIStackView()
|
|
33
29
|
private var categoryButtons: [String: UIButton] = [:] // key "" == All
|
|
34
30
|
private let collectionView: UICollectionView
|
|
35
|
-
private let spinner = UIActivityIndicatorView(style: .large)
|
|
36
31
|
private let emptyLabel = UILabel()
|
|
37
|
-
private let errorLabel = UILabel()
|
|
38
|
-
private let retryButton = UIButton(type: .system)
|
|
39
|
-
private let attributionLabel = UILabel()
|
|
40
32
|
private let stateContainer = UIView()
|
|
41
33
|
|
|
42
|
-
private static let cellReuseID = "
|
|
43
|
-
private static let columns =
|
|
44
|
-
private static let cellSpacing: CGFloat =
|
|
34
|
+
private static let cellReuseID = "BundledStickerCell"
|
|
35
|
+
private static let columns = 5
|
|
36
|
+
private static let cellSpacing: CGFloat = 6
|
|
45
37
|
|
|
46
|
-
init() {
|
|
38
|
+
init(runtimeStickers: [RuntimeSticker] = []) {
|
|
39
|
+
self.runtimeStickers = runtimeStickers
|
|
47
40
|
let layout = UICollectionViewFlowLayout()
|
|
48
41
|
layout.minimumInteritemSpacing = Self.cellSpacing
|
|
49
42
|
layout.minimumLineSpacing = Self.cellSpacing
|
|
@@ -56,9 +49,16 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
56
49
|
|
|
57
50
|
override func viewDidLoad() {
|
|
58
51
|
super.viewDidLoad()
|
|
59
|
-
view.backgroundColor = DesignTokens.
|
|
52
|
+
view.backgroundColor = DesignTokens.elevatedPanel
|
|
60
53
|
buildUI()
|
|
61
|
-
|
|
54
|
+
loadLocalStickers()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override func viewDidDisappear(_ animated: Bool) {
|
|
58
|
+
super.viewDidDisappear(animated)
|
|
59
|
+
guard (isBeingDismissed || presentingViewController == nil), !didNotifyDismiss else { return }
|
|
60
|
+
didNotifyDismiss = true
|
|
61
|
+
onDismiss?()
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// MARK: - UI construction
|
|
@@ -86,9 +86,7 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
86
86
|
collectionView.backgroundColor = .clear
|
|
87
87
|
collectionView.dataSource = self
|
|
88
88
|
collectionView.delegate = self
|
|
89
|
-
collectionView.register(
|
|
90
|
-
|
|
91
|
-
spinner.hidesWhenStopped = true
|
|
89
|
+
collectionView.register(BundledStickerCell.self, forCellWithReuseIdentifier: Self.cellReuseID)
|
|
92
90
|
|
|
93
91
|
emptyLabel.text = "No stickers found."
|
|
94
92
|
emptyLabel.textColor = DesignTokens.onSurfaceVariant
|
|
@@ -96,19 +94,7 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
96
94
|
emptyLabel.font = .systemFont(ofSize: 14)
|
|
97
95
|
emptyLabel.isHidden = true
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
errorLabel.textColor = DesignTokens.onSurfaceVariant
|
|
101
|
-
errorLabel.textAlignment = .center
|
|
102
|
-
errorLabel.font = .systemFont(ofSize: 14)
|
|
103
|
-
errorLabel.numberOfLines = 0
|
|
104
|
-
errorLabel.isHidden = true
|
|
105
|
-
|
|
106
|
-
retryButton.setTitle("Retry", for: .normal)
|
|
107
|
-
retryButton.setTitleColor(DesignTokens.primaryContainer, for: .normal)
|
|
108
|
-
retryButton.isHidden = true
|
|
109
|
-
retryButton.addAction(UIAction { [weak self] _ in self?.loadCatalog(forceRefresh: true) }, for: .touchUpInside)
|
|
110
|
-
|
|
111
|
-
let stateStack = UIStackView(arrangedSubviews: [spinner, emptyLabel, errorLabel, retryButton])
|
|
97
|
+
let stateStack = UIStackView(arrangedSubviews: [emptyLabel])
|
|
112
98
|
stateStack.axis = .vertical
|
|
113
99
|
stateStack.alignment = .center
|
|
114
100
|
stateStack.spacing = 12
|
|
@@ -121,14 +107,9 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
121
107
|
stateStack.trailingAnchor.constraint(lessThanOrEqualTo: stateContainer.trailingAnchor, constant: -24),
|
|
122
108
|
])
|
|
123
109
|
|
|
124
|
-
attributionLabel.text = "Stickers by OpenMoji (CC BY-SA 4.0)"
|
|
125
|
-
attributionLabel.font = .systemFont(ofSize: 11)
|
|
126
|
-
attributionLabel.textColor = DesignTokens.outline
|
|
127
|
-
attributionLabel.textAlignment = .center
|
|
128
|
-
|
|
129
110
|
// stateContainer (spinner/empty/error) overlays the collection view's area rather than
|
|
130
111
|
// sharing stack space, since it's shown/hidden independently of the grid's own layout.
|
|
131
|
-
let root = UIStackView(arrangedSubviews: [searchBar, categoryScroll, collectionView
|
|
112
|
+
let root = UIStackView(arrangedSubviews: [searchBar, categoryScroll, collectionView])
|
|
132
113
|
root.axis = .vertical
|
|
133
114
|
root.translatesAutoresizingMaskIntoConstraints = false
|
|
134
115
|
|
|
@@ -142,8 +123,6 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
142
123
|
root.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
|
|
143
124
|
root.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8),
|
|
144
125
|
categoryScroll.heightAnchor.constraint(equalToConstant: 40),
|
|
145
|
-
attributionLabel.heightAnchor.constraint(equalToConstant: 20),
|
|
146
|
-
|
|
147
126
|
stateContainer.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
|
|
148
127
|
stateContainer.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
|
|
149
128
|
stateContainer.topAnchor.constraint(equalTo: collectionView.topAnchor),
|
|
@@ -155,7 +134,9 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
155
134
|
categoryStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
156
135
|
categoryButtons.removeAll()
|
|
157
136
|
|
|
158
|
-
let
|
|
137
|
+
let localGroups = Set(localStickerNames.compactMap { $0.split(separator: "_").first.map { "local:" + $0.lowercased() } }).sorted()
|
|
138
|
+
let runtimeGroups = runtimeStickers.isEmpty ? [] : [("runtime", "My stickers")]
|
|
139
|
+
let all = [("", "All")] + runtimeGroups + localGroups.map { ($0, Self.displayName(for: $0.replacingOccurrences(of: "local:", with: ""))) }
|
|
159
140
|
all.forEach { key, title in
|
|
160
141
|
let chip = makeChip(title: title) { [weak self] in self?.selectCategory(key) }
|
|
161
142
|
categoryButtons[key] = chip
|
|
@@ -200,47 +181,22 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
200
181
|
}
|
|
201
182
|
}
|
|
202
183
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
self.applyFilter()
|
|
213
|
-
self.setLoadState(.loaded)
|
|
214
|
-
} else {
|
|
215
|
-
self.setLoadState(.error((error as NSError?)?.localizedDescription ?? "Couldn't load stickers — check your connection."))
|
|
216
|
-
}
|
|
217
|
-
}
|
|
184
|
+
private func loadLocalStickers() {
|
|
185
|
+
let hostBundle = Bundle(for: OnlineStickerSheetViewController.self)
|
|
186
|
+
let bundleURL = Bundle.main.url(forResource: "PhotoVideoEditorStickers", withExtension: "bundle")
|
|
187
|
+
?? hostBundle.url(forResource: "PhotoVideoEditorStickers", withExtension: "bundle")
|
|
188
|
+
guard let bundleURL, let bundle = Bundle(url: bundleURL) else { return }
|
|
189
|
+
localStickerNames = (bundle.urls(forResourcesWithExtension: "png", subdirectory: nil) ?? []).map(\.lastPathComponent).sorted()
|
|
190
|
+
displayedLocalStickerNames = localStickerNames
|
|
191
|
+
rebuildCategoryChips()
|
|
192
|
+
applyFilter()
|
|
218
193
|
}
|
|
219
194
|
|
|
220
|
-
private func
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
spinner.startAnimating()
|
|
226
|
-
emptyLabel.isHidden = true
|
|
227
|
-
errorLabel.isHidden = true
|
|
228
|
-
retryButton.isHidden = true
|
|
229
|
-
case .loaded:
|
|
230
|
-
spinner.stopAnimating()
|
|
231
|
-
let isEmpty = displayedEntries.isEmpty
|
|
232
|
-
stateContainer.isHidden = !isEmpty
|
|
233
|
-
emptyLabel.isHidden = !isEmpty
|
|
234
|
-
errorLabel.isHidden = true
|
|
235
|
-
retryButton.isHidden = true
|
|
236
|
-
case .error(let message):
|
|
237
|
-
spinner.stopAnimating()
|
|
238
|
-
stateContainer.isHidden = false
|
|
239
|
-
emptyLabel.isHidden = true
|
|
240
|
-
errorLabel.isHidden = false
|
|
241
|
-
errorLabel.text = message
|
|
242
|
-
retryButton.isHidden = false
|
|
243
|
-
}
|
|
195
|
+
private func localStickerURL(named name: String) -> URL? {
|
|
196
|
+
let hostBundle = Bundle(for: OnlineStickerSheetViewController.self)
|
|
197
|
+
let bundleURL = Bundle.main.url(forResource: "PhotoVideoEditorStickers", withExtension: "bundle")
|
|
198
|
+
?? hostBundle.url(forResource: "PhotoVideoEditorStickers", withExtension: "bundle")
|
|
199
|
+
return bundleURL.flatMap(Bundle.init(url:))?.url(forResource: (name as NSString).deletingPathExtension, withExtension: "png")
|
|
244
200
|
}
|
|
245
201
|
|
|
246
202
|
// MARK: - Filtering
|
|
@@ -254,47 +210,19 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
254
210
|
/// Search takes precedence over category when non-empty; otherwise filter by selected
|
|
255
211
|
/// category, with "All" (nil) meaning no filter.
|
|
256
212
|
private func applyFilter() {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
displayedEntries = allEntries
|
|
213
|
+
let normalizedQuery = searchQuery.lowercased()
|
|
214
|
+
displayedLocalStickerNames = localStickerNames.filter { name in
|
|
215
|
+
let matchesQuery = normalizedQuery.isEmpty || name.lowercased().contains(normalizedQuery)
|
|
216
|
+
let matchesGroup = selectedGroup == nil || (selectedGroup?.hasPrefix("local:") == true && name.lowercased().hasPrefix(selectedGroup!.replacingOccurrences(of: "local:", with: "")))
|
|
217
|
+
return matchesQuery && matchesGroup
|
|
263
218
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// MARK: - Sticker selection / download
|
|
269
|
-
|
|
270
|
-
private func stickerTapped(_ entry: OpenMojiEntry, at indexPath: IndexPath) {
|
|
271
|
-
guard downloadInFlightHexcode == nil else { return }
|
|
272
|
-
downloadInFlightHexcode = entry.hexcode
|
|
273
|
-
if let cell = collectionView.cellForItem(at: indexPath) as? OpenMojiCell {
|
|
274
|
-
cell.setDownloading(true)
|
|
275
|
-
}
|
|
276
|
-
let url = catalog.fullURL(entry.hexcode)
|
|
277
|
-
loader.downloadFull(url: url) { [weak self] fileURL, error in
|
|
278
|
-
guard let self else { return }
|
|
279
|
-
self.downloadInFlightHexcode = nil
|
|
280
|
-
if let cell = self.collectionView.cellForItem(at: indexPath) as? OpenMojiCell {
|
|
281
|
-
cell.setDownloading(false)
|
|
282
|
-
}
|
|
283
|
-
guard let fileURL, let data = try? Data(contentsOf: fileURL), let image = UIImage(data: data) else {
|
|
284
|
-
self.showInlineDownloadError()
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
// Write to a fresh temp file (consistent with the "Upload" flow's `writeTempImage`) so the
|
|
288
|
-
// caller always receives a `file://` URL it can treat like any other picked image, decoupled
|
|
289
|
-
// from our own disk cache's lifetime.
|
|
290
|
-
guard let tempURL = self.writeTempImage(image) else {
|
|
291
|
-
self.showInlineDownloadError()
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
|
-
self.dismiss(animated: true) { [weak self] in
|
|
295
|
-
self?.onStickerPicked?(tempURL, image)
|
|
296
|
-
}
|
|
219
|
+
displayedRuntimeStickers = runtimeStickers.filter { sticker in
|
|
220
|
+
(selectedGroup == nil || selectedGroup == "runtime") && (normalizedQuery.isEmpty || sticker.id.lowercased().contains(normalizedQuery))
|
|
297
221
|
}
|
|
222
|
+
collectionView.reloadData()
|
|
223
|
+
let isEmpty = displayedLocalStickerNames.isEmpty && displayedRuntimeStickers.isEmpty
|
|
224
|
+
stateContainer.isHidden = !isEmpty
|
|
225
|
+
emptyLabel.isHidden = !isEmpty
|
|
298
226
|
}
|
|
299
227
|
|
|
300
228
|
private func writeTempImage(_ image: UIImage) -> URL? {
|
|
@@ -308,11 +236,26 @@ final class OnlineStickerSheetViewController: UIViewController {
|
|
|
308
236
|
}
|
|
309
237
|
}
|
|
310
238
|
|
|
311
|
-
private func
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
239
|
+
private func loadRuntimeSticker(_ sticker: RuntimeSticker, completion: @escaping (URL?, UIImage?) -> Void) {
|
|
240
|
+
if !sticker.uri.lowercased().hasPrefix("https://") {
|
|
241
|
+
guard let path = SourceResolver.resolvePath(sourceUri: sticker.uri, tempPrefix: "pve_runtime_sticker") else {
|
|
242
|
+
completion(nil, nil)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
let localURL = URL(fileURLWithPath: path)
|
|
246
|
+
completion(localURL, UIImage(contentsOfFile: path))
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
guard let url = URL(string: sticker.uri) else { completion(nil, nil); return }
|
|
250
|
+
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
|
|
251
|
+
guard let self, let data, let image = UIImage(data: data), let localURL = self.writeTempImage(image) else {
|
|
252
|
+
DispatchQueue.main.async { completion(nil, nil) }
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
DispatchQueue.main.async { completion(localURL, image) }
|
|
256
|
+
}.resume()
|
|
315
257
|
}
|
|
258
|
+
|
|
316
259
|
}
|
|
317
260
|
|
|
318
261
|
// MARK: - UISearchBarDelegate (debounced)
|
|
@@ -344,20 +287,38 @@ extension OnlineStickerSheetViewController: UISearchBarDelegate {
|
|
|
344
287
|
|
|
345
288
|
extension OnlineStickerSheetViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
|
|
346
289
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
347
|
-
|
|
290
|
+
displayedRuntimeStickers.count + displayedLocalStickerNames.count
|
|
348
291
|
}
|
|
349
292
|
|
|
350
293
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
351
|
-
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.cellReuseID, for: indexPath) as!
|
|
352
|
-
|
|
353
|
-
|
|
294
|
+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.cellReuseID, for: indexPath) as! BundledStickerCell
|
|
295
|
+
if indexPath.item < displayedRuntimeStickers.count {
|
|
296
|
+
let sticker = displayedRuntimeStickers[indexPath.item]
|
|
297
|
+
cell.configure(image: nil)
|
|
298
|
+
loadRuntimeSticker(sticker) { [weak collectionView] _, image in
|
|
299
|
+
guard collectionView?.indexPath(for: cell) == indexPath else { return }
|
|
300
|
+
cell.configure(image: image)
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
let name = displayedLocalStickerNames[indexPath.item - displayedRuntimeStickers.count]
|
|
304
|
+
let image = localStickerURL(named: name).flatMap { UIImage(contentsOfFile: $0.path) }
|
|
305
|
+
cell.configure(image: image)
|
|
306
|
+
}
|
|
354
307
|
return cell
|
|
355
308
|
}
|
|
356
309
|
|
|
357
310
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
358
311
|
collectionView.deselectItem(at: indexPath, animated: true)
|
|
359
|
-
|
|
360
|
-
|
|
312
|
+
if indexPath.item < displayedRuntimeStickers.count {
|
|
313
|
+
loadRuntimeSticker(displayedRuntimeStickers[indexPath.item]) { [weak self] url, image in
|
|
314
|
+
guard let self, let url, let image else { return }
|
|
315
|
+
self.dismiss(animated: true) { [weak self] in self?.onStickerPicked?(url, image) }
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
let name = displayedLocalStickerNames[indexPath.item - displayedRuntimeStickers.count]
|
|
319
|
+
guard let url = localStickerURL(named: name), let image = UIImage(contentsOfFile: url.path), let tempURL = writeTempImage(image) else { return }
|
|
320
|
+
dismiss(animated: true) { [weak self] in self?.onStickerPicked?(tempURL, image) }
|
|
321
|
+
}
|
|
361
322
|
}
|
|
362
323
|
|
|
363
324
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
|
@@ -367,15 +328,13 @@ extension OnlineStickerSheetViewController: UICollectionViewDataSource, UICollec
|
|
|
367
328
|
}
|
|
368
329
|
}
|
|
369
330
|
|
|
370
|
-
/// One grid cell
|
|
371
|
-
|
|
372
|
-
private final class OpenMojiCell: UICollectionViewCell {
|
|
331
|
+
/// One grid cell for a bundled sticker image.
|
|
332
|
+
private final class BundledStickerCell: UICollectionViewCell {
|
|
373
333
|
private let imageView = UIImageView()
|
|
374
|
-
private let spinner = UIActivityIndicatorView(style: .medium)
|
|
375
334
|
|
|
376
335
|
override init(frame: CGRect) {
|
|
377
336
|
super.init(frame: frame)
|
|
378
|
-
contentView.backgroundColor = DesignTokens.
|
|
337
|
+
contentView.backgroundColor = DesignTokens.surfaceContainerLow
|
|
379
338
|
contentView.layer.cornerRadius = DesignTokens.radiusMd
|
|
380
339
|
contentView.clipsToBounds = true
|
|
381
340
|
|
|
@@ -389,30 +348,14 @@ private final class OpenMojiCell: UICollectionViewCell {
|
|
|
389
348
|
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4),
|
|
390
349
|
])
|
|
391
350
|
|
|
392
|
-
spinner.hidesWhenStopped = true
|
|
393
|
-
contentView.addSubview(spinner)
|
|
394
|
-
spinner.translatesAutoresizingMaskIntoConstraints = false
|
|
395
|
-
NSLayoutConstraint.activate([
|
|
396
|
-
spinner.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
397
|
-
spinner.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
|
398
|
-
])
|
|
399
351
|
}
|
|
400
352
|
|
|
401
353
|
required init?(coder: NSCoder) { nil }
|
|
402
354
|
|
|
403
|
-
func configure(
|
|
404
|
-
loader.loadThumbnail(url: url, into: imageView)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
func setDownloading(_ downloading: Bool) {
|
|
408
|
-
if downloading { spinner.startAnimating() } else { spinner.stopAnimating() }
|
|
409
|
-
imageView.alpha = downloading ? 0.4 : 1.0
|
|
410
|
-
}
|
|
355
|
+
func configure(image: UIImage?) { imageView.image = image }
|
|
411
356
|
|
|
412
357
|
override func prepareForReuse() {
|
|
413
358
|
super.prepareForReuse()
|
|
414
359
|
imageView.image = nil
|
|
415
|
-
spinner.stopAnimating()
|
|
416
|
-
imageView.alpha = 1.0
|
|
417
360
|
}
|
|
418
361
|
}
|
package/lib/module/index.js
CHANGED
|
@@ -16,8 +16,8 @@ const defaultFeatures = {
|
|
|
16
16
|
filters: true,
|
|
17
17
|
text: true,
|
|
18
18
|
stickers: true,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
draw: true,
|
|
20
|
+
overlays: true
|
|
21
21
|
};
|
|
22
22
|
function invalid(message) {
|
|
23
23
|
throw new PhotoVideoEditorError('E_INVALID_OPTIONS', message);
|