Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions ForPDA.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
66FC76ADFBEF155B4AD5684E /* SortType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124DA2BF5DC419EB71D063EE /* SortType.swift */; };
6757DAF4542B89F4E49037F5 /* SFSafeSymbols.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 322E22C74D44156FAE5F3F7B /* SFSafeSymbols.framework */; };
67A6B4599E52C547BA1DF046 /* FavoriteRootFeature+Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E759700E6C2878F605BBEC0 /* FavoriteRootFeature+Analytics.swift */; };
67BA9AB34F23890684D33638 /* TabViewGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A5F8365A1E77E822F6C62 /* TabViewGallery.swift */; };
67DCC37FFEA96AA2B7892715 /* ComposableArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42D2398CAA67D4000962E67 /* ComposableArchitecture.framework */; };
681BDDE56CBE8E4014DFE5E3 /* NotificationsFeature.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006917E2DFC70599AF512EA1 /* NotificationsFeature.framework */; };
68DF677F404827BB69F6F0FE /* Models.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD51C6E965A35DBF9F4FBA55 /* Models.framework */; };
Expand Down Expand Up @@ -659,6 +660,7 @@
C8C174A7838AA886FEC1D7D6 /* Models.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD51C6E965A35DBF9F4FBA55 /* Models.framework */; };
C9BB21B7E3604B43D7945AAE /* ArticleParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99F23D68A39F53CF4566F1A /* ArticleParser.swift */; };
CA65F52046767C63F6B38B83 /* LoginEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FD9D2DDE325720FC78D6A0 /* LoginEvent.swift */; };
CA8BBC8E71A6DF343FCE120A /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67862053B6EBC0E277C54CB2 /* ImageCollectionViewCell.swift */; };
CAA93B384FC9055B705904F7 /* ArticlesListFeature.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A39BD5202EED5434C358B34E /* ArticlesListFeature.framework */; };
CABBF3C4FAF86859FA053854 /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 98A2BB020C3E971CB0991A5E /* content.js */; };
CABDE1660FCCBA352BD96785 /* NotificationsClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 889505F119E7D81F1A1B9E41 /* NotificationsClient.framework */; };
Expand Down Expand Up @@ -729,6 +731,7 @@
DE4D4F43FA68F91A454AC76F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63C087325941C6CC8EDB1EA8 /* Preview Assets.xcassets */; };
DEC0DE86920768F3141234FE /* CombineSchedulers.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 94F0AD33CFEC2D542AD5BC7C /* CombineSchedulers.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DED7880C6B8C32503A0D4FBA /* ArticlePoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048460D126E2C26FEB6265DC /* ArticlePoll.swift */; };
DEEC77E32E9C1693FD3C2BC1 /* CustomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A389182FA02B9BAB9FAE0B /* CustomScrollView.swift */; };
DF4551A19CB85081DE3D30EB /* Cache_Cache.bundle in Dependencies */ = {isa = PBXBuildFile; fileRef = 4BAC16218EC9F9A9F2999ABB /* Cache_Cache.bundle */; };
DFD97D366B0725ADD3EFF5E6 /* HistoryFeature.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02A126A4634AB82351345379 /* HistoryFeature.framework */; };
DFE1FC541AF185F6935349F3 /* ForumRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F2AB27D0AE7F79E68C49FF /* ForumRow.swift */; };
Expand Down Expand Up @@ -2752,6 +2755,7 @@
67177EBF73A3EAA0B80AEE24 /* TuistFonts+CacheClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistFonts+CacheClient.swift"; sourceTree = "<group>"; };
6721D235B3FA36225B3C8144 /* TuistAssets+BookmarksFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+BookmarksFeature.swift"; sourceTree = "<group>"; };
67482FDA32E710F193B122C5 /* DeveloperFeature.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DeveloperFeature.framework; sourceTree = BUILT_PRODUCTS_DIR; };
67862053B6EBC0E277C54CB2 /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = "<group>"; };
67B79800FEE68FB8EA1B8F40 /* Localizable.xcstrings */ = {isa = PBXFileReference; path = Localizable.xcstrings; sourceTree = "<group>"; };
69D06482712444A605E2B9C6 /* TopicBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TopicBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6AD44E6D3049D0CB33C85211 /* MenuButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuButtons.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2791,6 +2795,7 @@
80EED22D45233E216DFED0EA /* TuistAssets+ArticleFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+ArticleFeature.swift"; sourceTree = "<group>"; };
812721A72ADCFCDF77E41819 /* QMSListFeature.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QMSListFeature.framework; sourceTree = BUILT_PRODUCTS_DIR; };
819C3F42D1C0667301523468 /* HistoryFeature-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "HistoryFeature-Info.plist"; sourceTree = "<group>"; };
81A389182FA02B9BAB9FAE0B /* CustomScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomScrollView.swift; sourceTree = "<group>"; };
82DB60D4B9083EC073BFC07C /* FavoritesRootEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesRootEvent.swift; sourceTree = "<group>"; };
83FB4761086E2397F004D41A /* SwiftyGif_SwiftyGif.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyGif_SwiftyGif.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
85CCC7B42618527254A75999 /* FavoritesFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesFeature.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2868,6 +2873,7 @@
B06C779165D24CB727795AEB /* TuistFonts+NotificationsFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistFonts+NotificationsFeature.swift"; sourceTree = "<group>"; };
B28971DBCF658964A52E32E7 /* ArticleElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleElementView.swift; sourceTree = "<group>"; };
B2A7090197422BB1A461D825 /* BBAttributedTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BBAttributedTokenizer.swift; sourceTree = "<group>"; };
B38A5F8365A1E77E822F6C62 /* TabViewGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewGallery.swift; sourceTree = "<group>"; };
B487A0FA81FA470EE3027064 /* TopicFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicFeature.swift; sourceTree = "<group>"; };
B4EC2971DC481D53169646D1 /* TuistFonts+NotificationsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistFonts+NotificationsClient.swift"; sourceTree = "<group>"; };
B55C906394BB2ECF59F0CEC9 /* ToastInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastInfo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3744,6 +3750,16 @@
path = Analytics;
sourceTree = "<group>";
};
18109C296EB581D582CE1747 /* Gallery */ = {
isa = PBXGroup;
children = (
81A389182FA02B9BAB9FAE0B /* CustomScrollView.swift */,
67862053B6EBC0E277C54CB2 /* ImageCollectionViewCell.swift */,
B38A5F8365A1E77E822F6C62 /* TabViewGallery.swift */,
);
path = Gallery;
sourceTree = "<group>";
};
1DB9BCAE34C7291DA91CE3BE /* Models */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4798,6 +4814,7 @@
BD12CB093819B7C6DC7B1FE3 /* Views */ = {
isa = PBXGroup;
children = (
18109C296EB581D582CE1747 /* Gallery */,
B28971DBCF658964A52E32E7 /* ArticleElementView.swift */,
101DA82283F6CEA438667EB7 /* ArticleMenu.swift */,
4DA58394E7006E74B711137B /* ParallaxHeader.swift */,
Expand Down Expand Up @@ -7182,6 +7199,9 @@
29DE94F1CF7D57B0C72C8948 /* ArticleMenuAction.swift in Sources */,
5FF953A9137820F5DBE72EEB /* ArticleElementView.swift in Sources */,
875B98063EE3978A99CF7680 /* ArticleMenu.swift in Sources */,
DEEC77E32E9C1693FD3C2BC1 /* CustomScrollView.swift in Sources */,
CA8BBC8E71A6DF343FCE120A /* ImageCollectionViewCell.swift in Sources */,
67BA9AB34F23890684D33638 /* TabViewGallery.swift in Sources */,
B5DB8BDE49B18806E1E790BA /* ParallaxHeader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
33 changes: 33 additions & 0 deletions Modules/Sources/ArticleFeature/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
{
"sourceLanguage" : "en",
"strings" : {
"" : {

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пустая локализация, скорее всего надо просто завернуть вызов в String

},
"%lld / %lld" : {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Либо переводим либо заворачиваем в стрингу

"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$lld / %2$lld"
}
}
}
},
"%lld people voted" : {
"localizations" : {
"ru" : {
Expand Down Expand Up @@ -163,6 +176,26 @@
}
}
},
"Save" : {
"localizations" : {
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Сохранить"
}
}
}
},
"Share" : {
"localizations" : {
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Поделиться"
}
}
}
},
"Share Link" : {
"localizations" : {
"ru" : {
Expand Down
20 changes: 19 additions & 1 deletion Modules/Sources/ArticleFeature/Views/ArticleElementView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct ArticleElementView: View {
@State private var gallerySelection: Int = 0
@State private var pollSelection: ArticlePoll.Option?
@State private var pollSelections: Set<ArticlePoll.Option> = .init()
@State private var showFullScreenImage: Bool = false
@State private var showFullScreenGallery: Bool = false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Что произойдет если мы их оба поставим на true? Мне кажется это нужно завернуть в Destination, посмотри примеры с шитами

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оставил только showFullScreenImage

@State private var selectedImageID: Int = 0

private var hasSelection: Bool {
return pollSelection != nil || !pollSelections.isEmpty
Expand Down Expand Up @@ -113,14 +116,20 @@ struct ArticleElementView: View {
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.width * element.ratioHW)
.clipped()
.onTapGesture {
showFullScreenImage.toggle()
}
.fullScreenCover(isPresented: $showFullScreenImage) {
TabViewGallery(gallery: [element.url], showScreenGallery: $showFullScreenImage, selectedImageID: $selectedImageID)
}
}

// MARK: - Gallery

@ViewBuilder
private func gallery(_ element: [ImageElement]) -> some View {
TabView {
ForEach(element, id: \.self) { imageElement in
ForEach(Array(element.enumerated()), id: \.element) { index, imageElement in
LazyImage(url: imageElement.url) { state in
Group {
if let image = state.image {
Expand All @@ -133,13 +142,22 @@ struct ArticleElementView: View {
}
.aspectRatio(imageElement.ratioWH, contentMode: .fit)
.clipped()
.highPriorityGesture(
TapGesture().onEnded {
showFullScreenGallery.toggle()
selectedImageID = index
}
)
}
.padding(.bottom, 48) // Fix against index overlaying
}
.frame(height: CGFloat(element.max(by: { $0.ratioHW < $1.ratioHW})!.ratioHW) * UIScreen.main.bounds.width + 48)
.tabViewStyle(.page(indexDisplayMode: .always))
.indexViewStyle(.page(backgroundDisplayMode: .always))
.padding(.bottom, -16)
.fullScreenCover(isPresented: $showFullScreenGallery) {
TabViewGallery(gallery: element.map{ $0.url }, showScreenGallery: $showFullScreenGallery, selectedImageID: $selectedImageID)
}
}

// MARK: - Video
Expand Down
179 changes: 179 additions & 0 deletions Modules/Sources/ArticleFeature/Views/Gallery/CustomScrollView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//
// CustomScrollView.swift
// ArticleFeature
//
// Created by Виталий Канин on 11.03.2025.
//

import SwiftUI
import UIKit
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По идее import UIKit можно убрать

import Models

struct CustomScrollView: UIViewRepresentable {

let imageElement: [URL]
@Binding var selectedIndex: Int
@Binding var isZooming: Bool
@Binding var isTouched: Bool
@Binding var backgroundOpacity: Double
var onClose: (() -> Void)?

func makeUIView(context: Context) -> UICollectionView {

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.itemSize = UIScreen.main.bounds.size

let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.backgroundColor = .black
collectionView.register(ImageCollectionViewCell.self, forCellWithReuseIdentifier: "ImageCollectionViewCell")

let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleVerticalSwipe(_:)))
panGesture.delegate = context.coordinator as any UIGestureRecognizerDelegate
collectionView.addGestureRecognizer(panGesture)

return collectionView
}

func updateUIView(_ uiView: UICollectionView, context: Context) {
let indexPath = IndexPath(item: selectedIndex, section: 0)
DispatchQueue.main.async {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отдаем предпочтение Task { @MainActor in
Перепроверяем что с ним все работает также

uiView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate{
var parent: CustomScrollView
private var initialTouchPoint: CGPoint = .zero
private var firstSwipeDirection: SwipeDirection = .none

enum SwipeDirection {
case horizontal
case vertical
case none
}

init(_ parent: CustomScrollView) {
self.parent = parent
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parent.imageElement.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
cell.setImage(url: parent.imageElement[indexPath.item])

cell.onZoom = { isZooming in
DispatchQueue.main.async {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Коммент выше

self.parent.isZooming = isZooming
}
}

cell.onToolBar = {
DispatchQueue.main.async {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Коммент выше

self.parent.isTouched.toggle()
}
}
return cell
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageIndex = Int(scrollView.contentOffset.x / scrollView.bounds.width)
self.parent.selectedIndex = pageIndex
scrollView.gestureRecognizers!.last!.isEnabled = true
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Когда это может вылететь?

firstSwipeDirection = .none
}

@objc func handleVerticalSwipe(_ gesture: UIPanGestureRecognizer) {
guard let collectionView = gesture.view as? UICollectionView else { return }
guard let visibleCell = collectionView.visibleCells.first as? ImageCollectionViewCell else { return }
let translation = gesture.translation(in: gesture.view?.superview)
if parent.isZooming { return }

switch gesture.state {
case .began:
initialTouchPoint = gesture.location(in: gesture.view?.superview)
case .changed:
if abs(translation.y) > abs(translation.x) && firstSwipeDirection == .vertical {
collectionView.isScrollEnabled = false
visibleCell.transform = CGAffineTransform(translationX: 0, y: translation.y)
self.parent.backgroundOpacity = max(0.1, 1 - Double(abs(translation.y * 5) / 700))
collectionView.layer.opacity = max(0.1, 1 - Float(abs(translation.y * 2.5) / 700))
}
case .ended, .cancelled:
if abs(translation.y) > 150 {
parent.onClose?()
UIView.animate(withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.3,
options: .curveEaseInOut,
animations: {
self.parent.backgroundOpacity = 0.0
collectionView.layer.opacity = 0.0
})
} else {
UIView.animate(withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.3,
options: .curveEaseInOut,
animations: {
visibleCell.transform = CGAffineTransform(translationX: 0, y: 0)
self.parent.backgroundOpacity = 1.0
collectionView.layer.opacity = 1.0
})
}
firstSwipeDirection = .none
collectionView.isScrollEnabled = true
case .failed:
print("failed")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если не пользуемся то:

case .failed, .possible:
  break

case .possible:
print("possible")
@unknown default:
break
}
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard let collection = otherGestureRecognizer.view as? UICollectionView else {
return false
}

if parent.imageElement.count == 1 {
firstSwipeDirection = .vertical
return true
}

if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {
let velocity = panGesture.velocity(in: panGesture.view)
if firstSwipeDirection == .none {
if abs(velocity.x) > abs(velocity.y) {
firstSwipeDirection = .horizontal
collection.isScrollEnabled = true
panGesture.isEnabled = false //
} else if abs(velocity.x) < abs(velocity.y) {
firstSwipeDirection = .vertical
otherGestureRecognizer.isEnabled = true
collection.isScrollEnabled = false
}
}
}

return true
}
}
}
Loading