From 3ce26a9faf2b9dd9c098f16abf3dd027ace5f933 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Fri, 8 Sep 2023 16:45:48 +0200 Subject: [PATCH] Avatar Bloom View in home screen navigation bar (#1630) * experimental code * veyr close to a perfect solution but i need to make it crop * code improvementds * for now this looks like the best solution but there is a weird animation happening * hard edge solution that completely works. * dark mode support * 17 support * code improvement and dark mode improvement * bloom gradient, it works but it needs a fix for landscape + the gradient should get hidden while in search mode * hides when searching * crash free constraint * fixed everything only the scroll bar issue needs fixing now * oblique gradient effect * removed unused code * removed unused code * updated the bloom effects * hairline restored for everything aside from the home screen * code improvements * possible final implementation * pr suggestion * hairline view added * Update ElementX/Sources/Other/ScrollViewAdapter.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * code adjustments * typo fix * typo fix --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> --- ElementX.xcodeproj/project.pbxproj | 20 ++-- .../Sources/Other/ScrollViewAdapter.swift | 8 ++ .../Screens/HomeScreen/View/BloomView.swift | 43 ++++++++ .../Screens/HomeScreen/View/HomeScreen.swift | 99 ++++++++++++++++++- 4 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 ElementX/Sources/Screens/HomeScreen/View/BloomView.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 074ba56d4..2a50d9b6d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -510,6 +510,7 @@ A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; + A762491D2AAB3BF700ADB9E6 /* BloomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A762491C2AAB3BF700ADB9E6 /* BloomView.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; @@ -902,7 +903,7 @@ 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -1052,7 +1053,7 @@ 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; - 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; }; + 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; }; 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -1242,7 +1243,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1312,6 +1313,7 @@ A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; + A762491C2AAB3BF700ADB9E6 /* BloomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloomView.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; @@ -1355,7 +1357,7 @@ B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -1443,7 +1445,7 @@ CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1523,7 +1525,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -1537,7 +1539,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -2323,6 +2325,7 @@ ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */, C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */, F3BC2D3573D900A9C9F8C191 /* HomeScreenUserMenuButton.swift */, + A762491C2AAB3BF700ADB9E6 /* BloomView.swift */, ); path = View; sourceTree = ""; @@ -4684,6 +4687,7 @@ 7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */, 7E2BB42805C59DB57E95610F /* PillView.swift in Sources */, 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */, + A762491D2AAB3BF700ADB9E6 /* BloomView.swift in Sources */, 1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */, 16CBD087038DE3815CDA512C /* PollMock.swift in Sources */, 6B4BF4A6450F55939B49FAEF /* PollOptionView.swift in Sources */, diff --git a/ElementX/Sources/Other/ScrollViewAdapter.swift b/ElementX/Sources/Other/ScrollViewAdapter.swift index 1256d3ea5..7e84515ed 100644 --- a/ElementX/Sources/Other/ScrollViewAdapter.swift +++ b/ElementX/Sources/Other/ScrollViewAdapter.swift @@ -49,11 +49,19 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate { .eraseToAnyPublisher() } + private let isAtTopEdgeSubject: CurrentValueSubject = .init(false) + var isAtTopEdge: CurrentValuePublisher { + isAtTopEdgeSubject + .asCurrentValuePublisher() + } + // MARK: - UIScrollViewDelegate func scrollViewDidScroll(_ scrollView: UIScrollView) { didScrollSubject.send(()) updateScrollDirection(scrollView) + let insetContentOffset = scrollView.contentOffset.y + scrollView.contentInset.top + isAtTopEdgeSubject.send(insetContentOffset >= 3) } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { diff --git a/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift b/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift new file mode 100644 index 000000000..6b27dcc8c --- /dev/null +++ b/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift @@ -0,0 +1,43 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct BloomView: View { + @ObservedObject var context: HomeScreenViewModel.Context + @Environment(\.colorScheme) private var colorScheme + + var body: some View { + ZStack { + avatar + .blur(radius: 64) + .blendMode(colorScheme == .dark ? .exclusion : .hardLight) + .opacity(colorScheme == .dark ? 0.50 : 0.20) + avatar + .blur(radius: 64) + .blendMode(.color) + .opacity(colorScheme == .dark ? 0.20 : 0.80) + } + } + + private var avatar: some View { + LoadableAvatarImage(url: context.viewState.userAvatarURL, + name: context.viewState.userDisplayName, + contentID: context.viewState.userID, + avatarSize: .custom(256), + imageProvider: context.imageProvider) + } +} diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index aa3b8dde7..10b3ed02d 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -25,6 +25,13 @@ struct HomeScreen: View { @State private var scrollViewAdapter = ScrollViewAdapter() @State private var isSearching = false + // Bloom components + @State private var bloomView: UIView? + @State private var leftBarButtonView: UIView? + @State private var gradientView: UIView? + @State private var navigationBarContainer: UIView? + @State private var hairlineView: UIView? + var body: some View { GeometryReader { geometry in ScrollView { @@ -56,7 +63,7 @@ struct HomeScreen: View { .disableAutocorrection(true) } } - .introspect(.scrollView, on: .iOS(.v16)) { scrollView in + .introspect(.scrollView, on: .iOS(.v16, .v17)) { scrollView in guard scrollView != scrollViewAdapter.scrollView else { return } scrollViewAdapter.scrollView = scrollView } @@ -87,10 +94,96 @@ struct HomeScreen: View { .toolbar { toolbar } .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .track(screen: .home) + .introspect(.viewController, on: .iOS(.v16, .v17)) { controller in + Task { + if bloomView == nil { + makeBloomView(controller: controller) + } + } + let isHidden = controller.navigationController?.topViewController != controller || isSearching + bloomView?.isHidden = isHidden + gradientView?.isHidden = isHidden + navigationBarContainer?.clipsToBounds = !isHidden + hairlineView?.isHidden = isHidden || !scrollViewAdapter.isAtTopEdge.value + if !isHidden { + updateBloomCenter() + } + } + .onReceive(scrollViewAdapter.isAtTopEdge.removeDuplicates()) { value in + hairlineView?.isHidden = !value + guard let gradientView else { + return + } + if value { + UIView.transition(with: gradientView, duration: 0.3, options: .curveEaseIn) { + gradientView.alpha = 0 + } + } else { + gradientView.alpha = 1 + } + } } // MARK: - Private + private var bloomGradient: some View { + LinearGradient(colors: [.clear, .compound.bgCanvasDefault], startPoint: .top, endPoint: .bottom) + .mask { + LinearGradient(stops: [.init(color: .white, location: 0.75), .init(color: .clear, location: 1.0)], + startPoint: .leading, + endPoint: .trailing) + } + .ignoresSafeArea(edges: .all) + } + + private func makeBloomView(controller: UIViewController) { + guard let navigationBarContainer = controller.navigationController?.navigationBar.subviews.first, + let leftBarButtonView = controller.navigationItem.leadingItemGroups.first?.barButtonItems.first?.customView else { + return + } + + let bloomController = UIHostingController(rootView: bloom) + bloomController.view.translatesAutoresizingMaskIntoConstraints = true + bloomController.view.backgroundColor = .clear + navigationBarContainer.insertSubview(bloomController.view, at: 0) + self.leftBarButtonView = leftBarButtonView + bloomView = bloomController.view + self.navigationBarContainer = navigationBarContainer + updateBloomCenter() + + let gradientController = UIHostingController(rootView: bloomGradient) + gradientController.view.backgroundColor = .clear + gradientController.view.translatesAutoresizingMaskIntoConstraints = false + navigationBarContainer.insertSubview(gradientController.view, aboveSubview: bloomController.view) + + let constraints = [gradientController.view.bottomAnchor.constraint(equalTo: navigationBarContainer.bottomAnchor), + gradientController.view.trailingAnchor.constraint(equalTo: navigationBarContainer.trailingAnchor), + gradientController.view.leadingAnchor.constraint(equalTo: navigationBarContainer.leadingAnchor), + gradientController.view.heightAnchor.constraint(equalToConstant: 40)] + constraints.forEach { $0.isActive = true } + gradientView = gradientController.view + + let dividerController = UIHostingController(rootView: Divider().ignoresSafeArea()) + dividerController.view.translatesAutoresizingMaskIntoConstraints = false + navigationBarContainer.addSubview(dividerController.view) + let dividerConstraints = [dividerController.view.bottomAnchor.constraint(equalTo: gradientController.view.bottomAnchor), + dividerController.view.widthAnchor.constraint(equalTo: gradientController.view.widthAnchor), + dividerController.view.leadingAnchor.constraint(equalTo: gradientController.view.leadingAnchor)] + dividerConstraints.forEach { $0.isActive = true } + hairlineView = dividerController.view + } + + private func updateBloomCenter() { + guard let leftBarButtonView, + let bloomView, + let navigationBarContainer = bloomView.superview else { + return + } + + let center = leftBarButtonView.convert(leftBarButtonView.center, to: navigationBarContainer.coordinateSpace) + bloomView.center = center + } + @ViewBuilder /// The session verification banner and invites button if either are needed. private var topSection: some View { @@ -155,6 +248,10 @@ struct HomeScreen: View { } } + private var bloom: some View { + BloomView(context: context) + } + private var newRoomButton: some View { Button { context.send(viewAction: .startChat)