Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [FC-0047] Full-Bleed Header + Top Navigation #385

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
8 changes: 8 additions & 0 deletions Core/Core.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
020306CC2932C0C4000949EA /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020306CB2932C0C4000949EA /* PickerView.swift */; };
02066B482906F73400F4307E /* PickerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02066B472906F73400F4307E /* PickerMenu.swift */; };
020C31C9290AC3F700D6DEA2 /* PickerFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020C31C8290AC3F700D6DEA2 /* PickerFields.swift */; };
020D72F42BB76DFE00773319 /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020D72F32BB76DFE00773319 /* VisualEffectView.swift */; };
021D924828DC860C00ACC565 /* Data_UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D924728DC860C00ACC565 /* Data_UserProfile.swift */; };
021D925028DC89D100ACC565 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D924F28DC89D100ACC565 /* UserProfile.swift */; };
021D925728DCF12900ACC565 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D925628DCF12900ACC565 /* AlertView.swift */; };
Expand Down Expand Up @@ -76,6 +77,7 @@
02CF46C829546AA200A698EE /* NoCachedDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CF46C729546AA200A698EE /* NoCachedDataError.swift */; };
02D400612B0678190029D168 /* SKStoreReviewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */; };
02D800CC29348F460099CF16 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D800CB29348F460099CF16 /* ImagePicker.swift */; };
02E224DB2BB76B3E00EF1ADB /* ResponsiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E224DA2BB76B3E00EF1ADB /* ResponsiveView.swift */; };
02E225B0291D29EB0067769A /* UrlExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E225AF291D29EB0067769A /* UrlExtension.swift */; };
02E93F852AEBAEBC006C4750 /* AppReviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E93F842AEBAEBC006C4750 /* AppReviewViewModel.swift */; };
02F164372902A9EB0090DDEF /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F164362902A9EB0090DDEF /* StringExtension.swift */; };
Expand Down Expand Up @@ -188,6 +190,7 @@
020306CB2932C0C4000949EA /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = "<group>"; };
02066B472906F73400F4307E /* PickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerMenu.swift; sourceTree = "<group>"; };
020C31C8290AC3F700D6DEA2 /* PickerFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerFields.swift; sourceTree = "<group>"; };
020D72F32BB76DFE00773319 /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = "<group>"; };
021D924728DC860C00ACC565 /* Data_UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_UserProfile.swift; sourceTree = "<group>"; };
021D924F28DC89D100ACC565 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
021D925628DCF12900ACC565 /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -253,6 +256,7 @@
02CF46C729546AA200A698EE /* NoCachedDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCachedDataError.swift; sourceTree = "<group>"; };
02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKStoreReviewControllerExtension.swift; sourceTree = "<group>"; };
02D800CB29348F460099CF16 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
02E224DA2BB76B3E00EF1ADB /* ResponsiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponsiveView.swift; sourceTree = "<group>"; };
02E225AF291D29EB0067769A /* UrlExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlExtension.swift; sourceTree = "<group>"; };
02E93F842AEBAEBC006C4750 /* AppReviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewViewModel.swift; sourceTree = "<group>"; };
02ED50CB29A64B84008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -725,6 +729,8 @@
BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */,
02E93F862AEBAED4006C4750 /* AppReview */,
BA981BCF2B91ED50005707C2 /* FullScreenProgressView.swift */,
02E224DA2BB76B3E00EF1ADB /* ResponsiveView.swift */,
020D72F32BB76DFE00773319 /* VisualEffectView.swift */,
);
path = Base;
sourceTree = "<group>";
Expand Down Expand Up @@ -1130,6 +1136,7 @@
0727877F28D25B24002E9142 /* Alamofire+Error.swift in Sources */,
02A4833829B8A8F900D33F33 /* CoreDataModel.xcdatamodeld in Sources */,
064987952B4D69FF0071642A /* SurveyCssInjection.swift in Sources */,
02E224DB2BB76B3E00EF1ADB /* ResponsiveView.swift in Sources */,
0259104A29C4A5B6004B5A55 /* UserSettings.swift in Sources */,
021D925028DC89D100ACC565 /* UserProfile.swift in Sources */,
071009D028D1E3A600344290 /* Constants.swift in Sources */,
Expand Down Expand Up @@ -1186,6 +1193,7 @@
024BE3DF29B2615500BCDEE2 /* CGColorExtension.swift in Sources */,
0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */,
07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */,
020D72F42BB76DFE00773319 /* VisualEffectView.swift in Sources */,
0727878928D31734002E9142 /* User.swift in Sources */,
A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */,
02280F5B294B4E6F0032823A /* Connectivity.swift in Sources */,
Expand Down
6 changes: 0 additions & 6 deletions Core/Core/Configuration/Config/UIComponentsConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,16 @@ import Foundation

private enum Keys: String, RawStringExtractable {
case courseNestedListEnabled = "COURSE_NESTED_LIST_ENABLED"
case courseTopTabBarEnabled = "COURSE_TOP_TAB_BAR_ENABLED"
case courseBannerEnabled = "COURSE_BANNER_ENABLED"
case courseUnitProgressEnabled = "COURSE_UNIT_PROGRESS_ENABLED"
}

public class UIComponentsConfig: NSObject {
public var courseNestedListEnabled: Bool
public var courseBannerEnabled: Bool
public var courseUnitProgressEnabled: Bool
public var courseTopTabBarEnabled: Bool

init(dictionary: [String: Any]) {
courseNestedListEnabled = dictionary[Keys.courseNestedListEnabled] as? Bool ?? false
courseBannerEnabled = dictionary[Keys.courseBannerEnabled] as? Bool ?? false
courseUnitProgressEnabled = dictionary[Keys.courseUnitProgressEnabled] as? Bool ?? false
courseTopTabBarEnabled = dictionary[Keys.courseTopTabBarEnabled] as? Bool ?? false
super.init()
}
}
Expand Down
5 changes: 4 additions & 1 deletion Core/Core/Domain/Model/CourseBlockModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public struct CourseStructure: Equatable {
public var childs: [CourseChapter]
public let media: DataLayer.CourseMedia //FIXME Domain model
public let certificate: Certificate?
public let org: String

public init(
id: String,
Expand All @@ -33,7 +34,8 @@ public struct CourseStructure: Equatable {
topicID: String? = nil,
childs: [CourseChapter],
media: DataLayer.CourseMedia,
certificate: Certificate?
certificate: Certificate?,
org: String
) {
self.id = id
self.graded = graded
Expand All @@ -45,6 +47,7 @@ public struct CourseStructure: Equatable {
self.childs = childs
self.media = media
self.certificate = certificate
self.org = org
}

public func totalVideosSizeInBytes(downloadQuality: DownloadQuality) -> Int {
Expand Down
57 changes: 57 additions & 0 deletions Core/Core/View/Base/ResponsiveView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// ResponsiveView.swift
// Core
//
// Created by  Stepanok Ivan on 26.03.2024.
//

import SwiftUI

public struct ResponsiveView: View {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to give it a meaningful name as every view is by default responsive view.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed to DynamicOffsetView. Good?


private let padHeight: CGFloat = 290
private let collapsedHorizontalHeight: CGFloat = 120
private let collapsedVerticalHeight: CGFloat = 90
private let expandedHeight: CGFloat = 240
private let coordinateBoundaryLower: CGFloat = -115
private let coordinateBoundaryUpper: CGFloat = 40

@Binding private var coordinate: CGFloat
@Binding private var collapsed: Bool
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@Environment(\.isHorizontal) private var isHorizontal

public init(coordinate: Binding<CGFloat>, collapsed: Binding<Bool>) {
self._coordinate = coordinate
self._collapsed = collapsed
}

public var body: some View {
VStack {
}.frame(
height: idiom == .pad
? padHeight
: (collapsed ? ( isHorizontal ? collapsedHorizontalHeight : collapsedVerticalHeight) : expandedHeight)
)
.overlay(
GeometryReader { geometry -> Color in
guard idiom != .pad else {
return Color.clear
Copy link
Contributor

Choose a reason for hiding this comment

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

Color is redundant, you can directly use .clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Fixed

}
guard !isHorizontal else {
coordinate = coordinateBoundaryLower
return Color.clear
}
DispatchQueue.main.async {
let minY = geometry.frame(in: .global).minY
if minY >= coordinateBoundaryLower && minY <= coordinateBoundaryUpper {
coordinate = minY
} else if minY <= coordinateBoundaryLower {
coordinate = coordinateBoundaryLower
}
}
return Color.clear
}
)
}
}
103 changes: 70 additions & 33 deletions Core/Core/View/Base/ScrollSlidingTabBar/ScrollSlidingTabBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,33 @@ import SwiftUI
import Theme

public struct ScrollSlidingTabBar: View {

@Binding private var selection: Int
@State private var buttonFrames: [Int: CGRect] = [:]
private let containerWidth: CGFloat
private let tabs: [String]
private let tabs: [(String, Image)]
private let style: Style
private let onTap: ((Int) -> Void)?

private var containerSpace: String {
return "container"
}

public init(
selection: Binding<Int>,
tabs: [String],
tabs: [(String, Image)],
style: Style = .default,
containerWidth: CGFloat,
onTap: ((Int) -> Void)? = nil) {
self._selection = selection
self.tabs = tabs
self.style = style
self.onTap = onTap
self.containerWidth = containerWidth
}
self._selection = selection
self.tabs = tabs
self.style = style
self.onTap = onTap
self.containerWidth = containerWidth
}

public var body: some View {
ZStack(alignment: .bottomLeading) {
Rectangle()
.fill(style.borderColor)
.frame(height: style.borderHeight, alignment: .leading)
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
Expand All @@ -50,6 +47,8 @@ public struct ScrollSlidingTabBar: View {
}
.coordinateSpace(name: containerSpace)
}
.onTapGesture {}
// Fix button tapable area bug – https://forums.developer.apple.com/forums/thread/745059
.onChange(of: selection) { newValue in
withAnimation {
proxy.scrollTo(newValue, anchor: .center)
Expand All @@ -66,20 +65,51 @@ extension ScrollSlidingTabBar {
private func buttons() -> some View {
HStack(spacing: 0) {
ForEach(Array(tabs.enumerated()), id: \.offset) { obj in
Button {
selection = obj.offset
onTap?(obj.offset)
} label: {
HStack {
Text(obj.element)
.font(isSelected(index: obj.offset) ? style.selectedFont : style.font)
Button(
action: {
selection = obj.offset
onTap?(obj.offset)
},
label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.foregroundStyle(
isSelected(index: obj.offset)
? style.activeAccentColor
: style.inactiveAccentColor
)
.onTapGesture {
selection = obj.offset
onTap?(obj.offset)
}
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(
isSelected(index: obj.offset)
? .clear
: style.borderColor,
lineWidth: style.borderHeight
)
)
HStack {
obj.element.1.renderingMode(.template)
.padding(.leading, 12)
Text(obj.element.0)
.padding(.trailing, 12)
.font(isSelected(index: obj.offset) ? style.selectedFont : style.font)
}
.accentColor(
isSelected(index: obj.offset)
? Theme.Colors.white
: Theme.Colors.slidingTextColor
)
}
.frame( height: 40)
.fixedSize(horizontal: true, vertical: true)
}
.padding(.horizontal, style.buttonHInset)
.padding(.vertical, style.buttonVInset)
}
.accentColor(
isSelected(index: obj.offset) ? style.activeAccentColor : style.inactiveAccentColor
)
.padding(.horizontal, style.buttonHInset)
.padding(.vertical, style.buttonVInset)
.readFrame(in: .named(containerSpace)) {
buttonFrames[obj.offset] = $0
}
Expand Down Expand Up @@ -165,15 +195,15 @@ extension ScrollSlidingTabBar {
}

public static let `default` = Style(
font: Theme.Fonts.bodyLarge,
selectedFont: Theme.Fonts.titleMedium,
font: Theme.Fonts.titleSmall,
selectedFont: Theme.Fonts.titleSmall,
activeAccentColor: Theme.Colors.accentXColor,
inactiveAccentColor: Theme.Colors.textSecondary,
indicatorHeight: 2,
borderColor: .gray.opacity(0.2),
inactiveAccentColor: Theme.Colors.background,
indicatorHeight: 0,
borderColor: Theme.Colors.slidingStrokeColor,
borderHeight: 1,
buttonHInset: 16,
buttonVInset: 10
buttonHInset: 4,
buttonVInset: 2
)
}
}
Expand All @@ -187,7 +217,14 @@ private struct SlidingTabConsumerView: View {
VStack(alignment: .leading) {
ScrollSlidingTabBar(
selection: $selection,
tabs: ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"],
tabs: [
("First", Image(systemName: "1.circle")),
("Second", Image(systemName: "2.circle")),
("Third", Image(systemName: "3.circle")),
("Fourth", Image(systemName: "4.circle")),
("Fifth", Image(systemName: "5.circle")),
("Sixth", Image(systemName: "6.circle"))
],
containerWidth: 300
)
TabView(selection: $selection) {
Expand Down
21 changes: 21 additions & 0 deletions Core/Core/View/Base/VisualEffectView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// VisualEffectView.swift
// Core
//
// Created by  Stepanok Ivan on 29.03.2024.
//

import SwiftUI

public struct VisualEffectView: UIViewRepresentable {
private var effect: UIVisualEffect?

public init(effect: UIVisualEffect? = nil) {
self.effect = effect
}

public func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
public func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {
uiView.effect = effect
}
}
Loading
Loading