Skip to content

Commit 5579173

Browse files
committed
WIP
1 parent 3a55ba7 commit 5579173

File tree

7 files changed

+737
-1230
lines changed

7 files changed

+737
-1230
lines changed

ForPDA.xcodeproj/project.pbxproj

Lines changed: 152 additions & 1227 deletions
Large diffs are not rendered by default.

Modules/Sources/ArticleFeature/Resources/Localizable.xcstrings

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
{
22
"sourceLanguage" : "en",
33
"strings" : {
4+
"" : {
5+
6+
},
7+
"%lld / %lld" : {
8+
"localizations" : {
9+
"en" : {
10+
"stringUnit" : {
11+
"state" : "new",
12+
"value" : "%1$lld / %2$lld"
13+
}
14+
}
15+
}
16+
},
417
"%lld people voted" : {
518
"localizations" : {
619
"ru" : {
@@ -161,6 +174,26 @@
161174
}
162175
}
163176
},
177+
"Save" : {
178+
"localizations" : {
179+
"ru" : {
180+
"stringUnit" : {
181+
"state" : "translated",
182+
"value" : "Сохранить"
183+
}
184+
}
185+
}
186+
},
187+
"Share" : {
188+
"localizations" : {
189+
"ru" : {
190+
"stringUnit" : {
191+
"state" : "translated",
192+
"value" : "Поделиться"
193+
}
194+
}
195+
}
196+
},
164197
"Share Link" : {
165198
"localizations" : {
166199
"ru" : {

Modules/Sources/ArticleFeature/Views/ArticleElementView.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ struct ArticleElementView: View {
2020
@State private var gallerySelection: Int = 0
2121
@State private var pollSelection: ArticlePoll.Option?
2222
@State private var pollSelections: Set<ArticlePoll.Option> = .init()
23+
@State private var showFullScreenImage: Bool = false
24+
@State private var showFullScreenGallery: Bool = false
25+
@State private var selectedImageID: Int = 0
2326

2427
private var hasSelection: Bool {
2528
return pollSelection != nil || !pollSelections.isEmpty
@@ -113,14 +116,27 @@ struct ArticleElementView: View {
113116
.frame(width: UIScreen.main.bounds.width,
114117
height: UIScreen.main.bounds.width * element.ratioHW)
115118
.clipped()
119+
.onTapGesture {
120+
showFullScreenImage.toggle()
121+
}
122+
.fullScreenCover(isPresented: $showFullScreenImage) {
123+
if #available(iOS 16.4, *) {
124+
// FullScreenImage(element: element, showFullScreen: $showFullScreenImage)
125+
// .presentationBackground(.clear)
126+
TabViewGallery(gallery: [element], showScreenGallery: $showFullScreenImage, selectedImageID: $selectedImageID)
127+
.presentationBackground(.clear)
128+
} else {
129+
// Fallback on earlier versions
130+
}
131+
}
116132
}
117133

118134
// MARK: - Gallery
119135

120136
@ViewBuilder
121137
private func gallery(_ element: [ImageElement]) -> some View {
122138
TabView {
123-
ForEach(element, id: \.self) { imageElement in
139+
ForEach(Array(element.enumerated()), id: \.element) { index, imageElement in
124140
LazyImage(url: imageElement.url) { state in
125141
Group {
126142
if let image = state.image {
@@ -133,13 +149,27 @@ struct ArticleElementView: View {
133149
}
134150
.aspectRatio(imageElement.ratioWH, contentMode: .fit)
135151
.clipped()
152+
.highPriorityGesture(
153+
TapGesture().onEnded {
154+
showFullScreenGallery.toggle()
155+
selectedImageID = index
156+
}
157+
)
136158
}
137159
.padding(.bottom, 48) // Fix against index overlaying
138160
}
139161
.frame(height: CGFloat(element.max(by: { $0.ratioHW < $1.ratioHW})!.ratioHW) * UIScreen.main.bounds.width + 48)
140162
.tabViewStyle(.page(indexDisplayMode: .always))
141163
.indexViewStyle(.page(backgroundDisplayMode: .always))
142164
.padding(.bottom, -16)
165+
.fullScreenCover(isPresented: $showFullScreenGallery) {
166+
if #available(iOS 16.4, *) {
167+
TabViewGallery(gallery: element, showScreenGallery: $showFullScreenGallery, selectedImageID: $selectedImageID)
168+
.presentationBackground(.clear)
169+
} else {
170+
// Fallback on earlier versions
171+
}
172+
}
143173
}
144174

145175
// MARK: - Video
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//
2+
// CustomScrollView.swift
3+
// ArticleFeature
4+
//
5+
// Created by Виталий Канин on 11.03.2025.
6+
//
7+
8+
import SwiftUI
9+
import UIKit
10+
import Models
11+
12+
struct CustomScrollView: UIViewRepresentable {
13+
14+
let imageElement: [ImageElement]
15+
@Binding var selectedIndex: Int
16+
@Binding var isZooming: Bool
17+
@Binding var isTouched: Bool
18+
@Binding var backgroundOpacity: Double
19+
var onClose: (() -> Void)?
20+
21+
func makeUIView(context: Context) -> UICollectionView {
22+
23+
let layout = UICollectionViewFlowLayout()
24+
layout.scrollDirection = .horizontal
25+
layout.minimumLineSpacing = 0
26+
layout.itemSize = UIScreen.main.bounds.size
27+
28+
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
29+
collectionView.isPagingEnabled = true
30+
collectionView.showsHorizontalScrollIndicator = false
31+
collectionView.showsVerticalScrollIndicator = false
32+
collectionView.backgroundColor = .clear
33+
collectionView.dataSource = context.coordinator
34+
collectionView.delegate = context.coordinator
35+
collectionView.backgroundColor = .black
36+
collectionView.register(ImageCollectionViewCell.self, forCellWithReuseIdentifier: "ImageCollectionViewCell")
37+
38+
let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleVerticalSwipe(_:)))
39+
panGesture.delegate = context.coordinator as any UIGestureRecognizerDelegate
40+
collectionView.addGestureRecognizer(panGesture)
41+
42+
return collectionView
43+
}
44+
45+
func updateUIView(_ uiView: UICollectionView, context: Context) {
46+
let indexPath = IndexPath(item: selectedIndex, section: 0)
47+
DispatchQueue.main.async {
48+
uiView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
49+
}
50+
}
51+
52+
func makeCoordinator() -> Coordinator {
53+
Coordinator(self)
54+
}
55+
56+
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate{
57+
var parent: CustomScrollView
58+
private var initialTouchPoint: CGPoint = .zero
59+
private var firstSwipeDirection: SwipeDirection = .none
60+
61+
enum SwipeDirection {
62+
case horizontal
63+
case vertical
64+
case none
65+
}
66+
67+
init(_ parent: CustomScrollView) {
68+
self.parent = parent
69+
}
70+
71+
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
72+
return parent.imageElement.count
73+
}
74+
75+
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
76+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
77+
cell.setImage(url: parent.imageElement[indexPath.item].url)
78+
79+
cell.onZoom = { isZooming in
80+
DispatchQueue.main.async {
81+
self.parent.isZooming = isZooming
82+
}
83+
}
84+
85+
cell.onToolBar = {
86+
DispatchQueue.main.async {
87+
self.parent.isTouched.toggle()
88+
}
89+
}
90+
return cell
91+
}
92+
93+
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
94+
let pageIndex = Int(scrollView.contentOffset.x / scrollView.bounds.width)
95+
self.parent.selectedIndex = pageIndex
96+
scrollView.gestureRecognizers!.last!.isEnabled = true
97+
firstSwipeDirection = .none
98+
}
99+
100+
@objc func handleVerticalSwipe(_ gesture: UIPanGestureRecognizer) {
101+
guard let collectionView = gesture.view as? UICollectionView else { return }
102+
guard let visibleCell = collectionView.visibleCells.first as? ImageCollectionViewCell else { return }
103+
let translation = gesture.translation(in: gesture.view?.superview)
104+
if parent.isZooming { return }
105+
106+
switch gesture.state {
107+
case .began:
108+
initialTouchPoint = gesture.location(in: gesture.view?.superview)
109+
case .changed:
110+
if abs(translation.y) > abs(translation.x) && firstSwipeDirection == .vertical {
111+
collectionView.isScrollEnabled = false
112+
visibleCell.transform = CGAffineTransform(translationX: 0, y: translation.y)
113+
self.parent.backgroundOpacity = max(0.1, 1 - Double(abs(translation.y * 5) / 700))
114+
collectionView.layer.opacity = max(0.1, 1 - Float(abs(translation.y * 2.5) / 700))
115+
}
116+
case .ended, .cancelled:
117+
if abs(translation.y) > 150 {
118+
parent.onClose?()
119+
UIView.animate(withDuration: 0.6,
120+
delay: 0,
121+
usingSpringWithDamping: 0.8,
122+
initialSpringVelocity: 0.3,
123+
options: .curveEaseInOut,
124+
animations: {
125+
self.parent.backgroundOpacity = 0.0
126+
collectionView.layer.opacity = 0.0
127+
})
128+
} else {
129+
UIView.animate(withDuration: 0.6,
130+
delay: 0,
131+
usingSpringWithDamping: 0.8,
132+
initialSpringVelocity: 0.3,
133+
options: .curveEaseInOut,
134+
animations: {
135+
visibleCell.transform = CGAffineTransform(translationX: 0, y: 0)
136+
self.parent.backgroundOpacity = 1.0
137+
collectionView.layer.opacity = 1.0
138+
})
139+
}
140+
firstSwipeDirection = .none
141+
collectionView.isScrollEnabled = true
142+
case .failed:
143+
print("failed")
144+
case .possible:
145+
print("possible")
146+
@unknown default:
147+
break
148+
}
149+
}
150+
151+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
152+
guard let collection = otherGestureRecognizer.view as? UICollectionView else {
153+
return false
154+
}
155+
156+
if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {
157+
let velocity = panGesture.velocity(in: panGesture.view)
158+
if firstSwipeDirection == .none {
159+
if abs(velocity.x) > abs(velocity.y) {
160+
firstSwipeDirection = .horizontal
161+
collection.isScrollEnabled = true
162+
panGesture.isEnabled = false //
163+
} else if abs(velocity.x) < abs(velocity.y) {
164+
firstSwipeDirection = .vertical
165+
otherGestureRecognizer.isEnabled = true
166+
collection.isScrollEnabled = false
167+
}
168+
}
169+
}
170+
return true
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)