From 488ec8e0093611376e1940e78382e8bbd9d47356 Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Fri, 14 Jul 2023 23:00:10 -0400 Subject: [PATCH] Fix image carousel to limit number of dots to not spill screen beyond visible margins Changelog-Fixed: Fix image carousel to limit number of dots to not spill screen beyond visible margins Closes: https://github.com/damus-io/damus/issues/1227 Closes: https://github.com/damus-io/damus/issues/1295 --- damus.xcodeproj/project.pbxproj | 4 ++ damus/Components/CarouselDotsView.swift | 65 +++++++++++++++++++++++++ damus/Components/ImageCarousel.swift | 27 ++-------- damus/Views/Images/ImageView.swift | 15 +----- 4 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 damus/Components/CarouselDotsView.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 20547031..112704cf 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; }; 3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; }; 3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAC7A012A60FE72002B50DF /* LocalizationUtilTests.swift */; }; + 3AAC7A042A626A75002B50DF /* CarouselDotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAC7A032A626A75002B50DF /* CarouselDotsView.swift */; }; 3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB72AB8298ECF30004BB58C /* Translator.swift */; }; 3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685A297633BC00C46468 /* InfoPlist.strings */; }; 3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; }; @@ -441,6 +442,7 @@ 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationService.swift; sourceTree = ""; }; 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLPlan.swift; sourceTree = ""; }; 3AAC7A012A60FE72002B50DF /* LocalizationUtilTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationUtilTests.swift; sourceTree = ""; }; + 3AAC7A032A626A75002B50DF /* CarouselDotsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselDotsView.swift; sourceTree = ""; }; 3AB5B86A2986D8A3006599D2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 3AB5B86B2986D8A3006599D2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 3AB5B86C2986D8A3006599D2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -1435,6 +1437,7 @@ 4C8D00C929DF80350036AF10 /* TruncatedText.swift */, 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */, 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */, + 3AAC7A032A626A75002B50DF /* CarouselDotsView.swift */, ); path = Components; sourceTree = ""; @@ -1876,6 +1879,7 @@ 4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */, 4C7FF7D52823313F009601DB /* Mentions.swift in Sources */, 4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */, + 3AAC7A042A626A75002B50DF /* CarouselDotsView.swift in Sources */, 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */, 3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */, 4C363A9028247A1D006E126D /* NostrLink.swift in Sources */, diff --git a/damus/Components/CarouselDotsView.swift b/damus/Components/CarouselDotsView.swift new file mode 100644 index 00000000..086b9f57 --- /dev/null +++ b/damus/Components/CarouselDotsView.swift @@ -0,0 +1,65 @@ +// +// CarouselDotsView.swift +// damus +// +// Created by Terry Yiu on 7/15/23. +// + +import SwiftUI + +struct CarouselDotsView: View { + let maxCount: Int + let maxVisibleCount: Int + @Binding var selectedIndex: Int + + var body: some View { + if maxCount > 1 { + HStack { + let visibleRange = visibleRange() + ForEach(0 ..< maxCount, id: \.self) { index in + if visibleRange.contains(index) { + Circle() + .fill(index == selectedIndex ? Color("DamusPurple") : Color("DamusLightGrey")) + .frame(width: 10, height: 10) + .onTapGesture { + selectedIndex = index + } + } + } + } + .padding(.top, CGFloat(8)) + .id(UUID()) + } + } + + private func visibleRange() -> ClosedRange { + let visibleCount = min(maxCount, maxVisibleCount) + + let half = Int(visibleCount / 2) + + // Keep the selected dot in the middle of the visible dots when possible. + var minVisibleIndex: Int + var maxVisibleIndex: Int + + if visibleCount % 2 == 0 { + minVisibleIndex = max(0, selectedIndex - half) + maxVisibleIndex = min(maxCount - 1, selectedIndex + half - 1) + } else { + minVisibleIndex = max(0, selectedIndex - half) + maxVisibleIndex = min(maxCount - 1, selectedIndex + half) + } + + // Adjust min and max to be within the bounds of what is visibly allowed. + if (maxVisibleIndex - minVisibleIndex + 1) < visibleCount { + if minVisibleIndex == 0 { + maxVisibleIndex = visibleCount - 1 + } else if maxVisibleIndex == maxCount - 1 { + minVisibleIndex = maxVisibleIndex - visibleCount + 1 + } + } else if (maxVisibleIndex - minVisibleIndex + 1) > visibleCount { + minVisibleIndex = maxVisibleIndex - maxVisibleCount + 1 + } + + return minVisibleIndex...maxVisibleIndex + } +} diff --git a/damus/Components/ImageCarousel.swift b/damus/Components/ImageCarousel.swift index 0bd4fde5..5bef70de 100644 --- a/damus/Components/ImageCarousel.swift +++ b/damus/Components/ImageCarousel.swift @@ -209,30 +209,9 @@ struct ImageCarousel: View { .onTapGesture { } // This is our custom carousel image indicator - CarouselDotsView(urls: urls, selectedIndex: $selectedIndex) - } - } -} - -// MARK: - Custom Carousel -struct CarouselDotsView: View { - let urls: [T] - @Binding var selectedIndex: Int - - var body: some View { - if urls.count > 1 { - HStack { - ForEach(urls.indices, id: \.self) { index in - Circle() - .fill(index == selectedIndex ? Color("DamusPurple") : Color("DamusLightGrey")) - .frame(width: 10, height: 10) - .onTapGesture { - selectedIndex = index - } - } - } - .padding(.top, CGFloat(8)) - .id(UUID()) + // A maximum of 18 should be visible. Any more than that and it starts to push the frame of the parent view + // causing adjacent views to disort in dimensions. + CarouselDotsView(maxCount: urls.count, maxVisibleCount: 18, selectedIndex: $selectedIndex) } } } diff --git a/damus/Views/Images/ImageView.swift b/damus/Views/Images/ImageView.swift index a51133c7..842c05c8 100644 --- a/damus/Views/Images/ImageView.swift +++ b/damus/Views/Images/ImageView.swift @@ -18,19 +18,6 @@ struct ImageView: View { let disable_animation: Bool - var tabViewIndicator: some View { - HStack(spacing: 10) { - ForEach(urls.indices, id: \.self) { index in - Capsule() - .fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary) - .frame(width: 7, height: 7) - } - } - .padding() - .background(.regularMaterial) - .clipShape(Capsule()) - } - var body: some View { ZStack { Color(.systemBackground) @@ -66,7 +53,7 @@ struct ImageView: View { Spacer() if (urls.count > 1) { - tabViewIndicator + CarouselDotsView(maxCount: urls.count, maxVisibleCount: 18, selectedIndex: $selectedIndex) } } }