Compare commits

...

28 Commits
1.0.1 ... main

Author SHA1 Message Date
Muhand Jumah 5534050e1d Removed Debug from currentIndex in HighlightingView 2023-05-05 22:33:23 -04:00
Muhand Jumah c09873eb81 FutureFlowDemo is now using 1.1.1 2023-04-30 14:58:01 -04:00
Muhand Jumah c596673a9c Excluded Examples folder from the package 2023-04-30 10:44:06 -04:00
Muhand Jumah 14e9591d5c
Update README.md 2023-04-30 10:37:00 -04:00
Muhand Jumah 20b92ec4ec
Update README.md 2023-04-30 10:35:39 -04:00
Muhand Jumah 52c779254f
Merge pull request #1 from xyfuture-llc/viewOnSpot
InstructionsView
2023-04-29 19:15:26 -04:00
Muhand Jumah 2e2e6bcf3f Added final result gif to README.md 2023-04-29 19:14:17 -04:00
Muhand Jumah 9004d99344 Added a gif to showoff the demo project 2023-04-29 19:12:39 -04:00
Muhand Jumah 29f602da95 Added a few more sections to README 2023-04-29 19:09:24 -04:00
Muhand Jumah 9c07be248f Fixed code block type 2023-04-29 19:04:46 -04:00
Muhand Jumah bd2d812c4a Added Installation and Usage docs 2023-04-29 19:02:44 -04:00
Muhand Jumah 8f1fd5f3fc Updated docs 2023-04-29 18:59:14 -04:00
Muhand Jumah 77deb1269e Implement optional viewPosition 2023-04-29 18:41:43 -04:00
Muhand Jumah 6c743e5e3f instructionsViewPosition is now optional 2023-04-29 18:39:13 -04:00
Muhand Jumah ec5668cc75 Added FutureFlow dependency to demo 2023-04-29 18:29:38 -04:00
Muhand Jumah 5cf1908c6a Exlcude Examples folder 2023-04-29 18:26:26 -04:00
Muhand Jumah 4d780de447 Moved library files/folders to root 2023-04-29 18:10:04 -04:00
Muhand Jumah 82d4f74658 Moved demo into its own directory 2023-04-29 18:09:19 -04:00
Muhand Jumah 8948af3260 Moved from library directory to root 2023-04-29 18:07:53 -04:00
Muhand Jumah e073ba8b40 removed root (duplicate) 2023-04-29 18:07:21 -04:00
Muhand Jumah 6fada57021 Removed xcworkspace 2023-04-29 18:06:48 -04:00
Muhand Jumah bbbfbcb7c5 Restructured everything 2023-04-29 18:03:10 -04:00
Muhand Jumah 903a063257 Added animation to the appearance and disappearance of instructions view 2023-04-27 10:57:44 -04:00
Muhand Jumah 5c147debcd add show tutorial as a state 2023-04-27 10:40:52 -04:00
Muhand Jumah 16d426d682 removed debug overlay 2023-04-27 10:25:49 -04:00
Muhand Jumah dc142d143f Fixed a comment 2023-04-27 10:23:53 -04:00
Muhand Jumah cad6d82289 Finished tutorial if reacehd end 2023-04-27 10:22:56 -04:00
Muhand Jumah dfe3deae3b Removed looping from advance and previous 2023-04-27 10:22:27 -04:00
47 changed files with 1329 additions and 768 deletions

BIN
Examples/Assets/demo1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 KiB

View File

@ -17,6 +17,7 @@
B69AAB4529F31E6700026789 /* FutureFlow in Frameworks */ = {isa = PBXBuildFile; productRef = B69AAB4429F31E6700026789 /* FutureFlow */; };
B69AAB4729F42F8900026789 /* Demo2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69AAB4629F42F8900026789 /* Demo2.swift */; };
B69AAB4929F44D2A00026789 /* Demo10.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69AAB4829F44D2A00026789 /* Demo10.swift */; };
B6B551EE29FDCB6E006CA32D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = B6B551ED29FDCB6E006CA32D /* README.md */; };
B6C55E9729F5C75200E9E281 /* Demo11.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C55E9629F5C75200E9E281 /* Demo11.swift */; };
/* End PBXBuildFile section */
@ -38,6 +39,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
B643355129FDD16500E10D0E /* FutureFlow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FutureFlow; path = ../..; sourceTree = "<group>"; };
B643355229FDD35700E10D0E /* FutureFlow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FutureFlow; path = ../..; sourceTree = "<group>"; };
B69AAB1429F31CF300026789 /* FutureFlowDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FutureFlowDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
B69AAB1729F31CF300026789 /* FutureFlowDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureFlowDemoApp.swift; sourceTree = "<group>"; };
B69AAB1929F31CF300026789 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@ -50,6 +53,8 @@
B69AAB3429F31CF500026789 /* FutureFlowDemoUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureFlowDemoUITestsLaunchTests.swift; sourceTree = "<group>"; };
B69AAB4629F42F8900026789 /* Demo2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo2.swift; sourceTree = "<group>"; };
B69AAB4829F44D2A00026789 /* Demo10.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo10.swift; sourceTree = "<group>"; };
B6A3C7F729FEF19000FC384C /* FutureFlow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FutureFlow; path = ../..; sourceTree = "<group>"; };
B6B551ED29FDCB6E006CA32D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B6C55E9629F5C75200E9E281 /* Demo11.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo11.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -79,9 +84,20 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B643355029FDD16500E10D0E /* Packages */ = {
isa = PBXGroup;
children = (
B643355129FDD16500E10D0E /* FutureFlow */,
B643355229FDD35700E10D0E /* FutureFlow */,
B6A3C7F729FEF19000FC384C /* FutureFlow */,
);
name = Packages;
sourceTree = "<group>";
};
B69AAB0B29F31CF300026789 = {
isa = PBXGroup;
children = (
B643355029FDD16500E10D0E /* Packages */,
B69AAB1629F31CF300026789 /* FutureFlowDemo */,
B69AAB2729F31CF500026789 /* FutureFlowDemoTests */,
B69AAB3129F31CF500026789 /* FutureFlowDemoUITests */,
@ -103,13 +119,11 @@
B69AAB1629F31CF300026789 /* FutureFlowDemo */ = {
isa = PBXGroup;
children = (
B6B551EC29FDCB4E006CA32D /* NotRelated */,
B69AAB1729F31CF300026789 /* FutureFlowDemoApp.swift */,
B69AAB1929F31CF300026789 /* ContentView.swift */,
B69AAB4629F42F8900026789 /* Demo2.swift */,
B69AAB1D29F31CF400026789 /* Preview Content */,
B69AAB1B29F31CF400026789 /* Assets.xcassets */,
B69AAB4829F44D2A00026789 /* Demo10.swift */,
B6C55E9629F5C75200E9E281 /* Demo11.swift */,
);
path = FutureFlowDemo;
sourceTree = "<group>";
@ -146,6 +160,17 @@
name = Frameworks;
sourceTree = "<group>";
};
B6B551EC29FDCB4E006CA32D /* NotRelated */ = {
isa = PBXGroup;
children = (
B69AAB4829F44D2A00026789 /* Demo10.swift */,
B6C55E9629F5C75200E9E281 /* Demo11.swift */,
B69AAB4629F42F8900026789 /* Demo2.swift */,
B6B551ED29FDCB6E006CA32D /* README.md */,
);
path = NotRelated;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -253,6 +278,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B6B551EE29FDCB6E006CA32D /* README.md in Resources */,
B69AAB1F29F31CF400026789 /* Preview Assets.xcassets in Resources */,
B69AAB1C29F31CF400026789 /* Assets.xcassets in Resources */,
);

View File

@ -0,0 +1,317 @@
//
// ContentView.swift
// FutureFlowDemo
//
// Created by Muhand Jumah on 4/21/23.
//
import SwiftUI
import FutureFlow
struct ParentWrapper: InstructionsView {
@Binding var tutorial: TutorialChunks
let next: () -> ()
let back: () -> ()
@State private var instructions: [String] = []
@Namespace var ns
var body: some View {
RectanglesTutorial(instructions: self.$instructions)
.onDone({ withNext in
if(withNext) {
self.next()
} else {
self.back()
}
})
.animation(.easeOut, value: self.tutorial)
.onAppear {
switch self.tutorial {
case .red1:
self.instructions = ["This is red"]
break
case .red2:
self.instructions = ["Red is purple now??? WHAT?"]
break
case .red3:
self.instructions = ["No way, IT IS BLUE!!"]
break
case .yellow:
self.instructions = ["This is yellow"]
break
case .green:
self.instructions = ["This is green"]
break
}
}
.onChange(of: self.tutorial) { newValue in
switch newValue {
case .red1:
self.instructions = ["This is red"]
break
case .red2:
self.instructions = ["Red is purple now??? WHAT?"]
break
case .red3:
self.instructions = ["No way, IT IS BLUE!!"]
break
case .yellow:
self.instructions = ["This is yellow"]
break
case .green:
self.instructions = ["This is green"]
break
}
}
}
}
enum TutorialChunks: FlowChunk, CaseIterable {
case red1
case red2
case red3
case green
case yellow
var spotlightShape: SpotlightShape {
switch self {
case .yellow:
return .circle()
default:
return .rectangle()
}
}
var spotlightBackground: SpotlightBackground {
switch self {
case .green:
return .ultraThinMaterial
default:
return .black
}
}
var instructionsViewPosition: InstructionsViewPosition? {
switch self {
case .red1, .red2, .red3:
return .below
case .green:
return .above
case .yellow:
return .above
}
}
func instructionsView(_ next: @escaping () -> (), _ back: @escaping () -> ()) -> FutureFlow.AnyInstructionsView? {
switch self {
case .red1, .red2:
ParentWrapper(tutorial:
.init(
get: {
self
}, set: { _ in }
),
next: next,
back: back
)
default:
ParentWrapper(tutorial:
.init(
get: {
self
}, set: { _ in }
),
next: next,
back: back
)
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .red1, .red2, .red3:
hasher.combine(0)
case .green:
hasher.combine(1)
case .yellow:
hasher.combine(2)
}
}
}
struct ContentView: View {
@State private var rectColor: Color = .red
private var uniqueIdentifier: String = UUID().uuidString
@State private var showTutorial: Bool = true
@Namespace var namespace
var body: some View {
VStack(spacing: 50) {
Rectangle()
.frame(width: 256, height: 256, alignment: .center)
.foregroundColor(self.rectColor)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.red1
)
Rectangle()
.frame(width: 64, height: 128, alignment: .center)
.foregroundColor(Color.green)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.green
)
Circle()
.frame(width: 128, height: 128, alignment: .center)
.foregroundColor(.yellow)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.yellow
)
.onTapGesture {
// withAnimation(.linear) {
self.showTutorial = true
// }
}
}
.padding()
.assembleSpotlightChunks(namespace:self.namespace, uniqueIdentifier: self.uniqueIdentifier, chunks: Array(TutorialChunks.allCases), showTutorial: self.$showTutorial) {
chunk in
withAnimation(.easeOut) {
switch chunk {
case .red2:
self.rectColor = .purple
break
case .red3:
self.rectColor = .blue
break
default:
self.rectColor = .red
break
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
internal struct RectanglesTutorial: InstructionsView {
@Binding private var instructions: [String]
@Namespace var positionNamespace
private var positionId: String = "steps-position-id"
@State private var showNext: Bool = true
@State private var showPrevious: Bool = true
@State private var currentStep: Int = 0
private var onDone: ((_ withNext: Bool) -> ())?
internal init(instructions: Binding<[String]>) {
self._instructions = instructions
// self.onDone = { _ in }
self.onDone = nil
}
fileprivate init(instructions: Binding<[String]>, onDone: @escaping (_ withNext: Bool) -> ()) {
self._instructions = instructions
self.onDone = onDone
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "lightbulb.circle.fill")
.font(.system(size: 32))
.foregroundColor(.blue)
if(self.currentStep < self.instructions.count) {
Text(self.instructions[self.currentStep])
.font(.body)
.animation(.easeOut, value: self.currentStep)
}
}
.matchedGeometryEffect(id: positionId, in: self.positionNamespace, properties: .position, anchor: .bottomTrailing, isSource: true)
HStack(alignment: .center, spacing:15) {
if(self.showPrevious) {
Button(action: {
guard self.currentStep > 0 else {
if let onDone = self.onDone {
onDone(false)
}
return
}
self.currentStep -= 1
}) {
Text("Previous")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.blue)
}
}
if(self.showNext) {
Button(action: {
guard self.currentStep < self.instructions.count - 1 else {
if let onDone = self.onDone {
onDone(true)
}
return
}
self.currentStep += 1
}) {
Text("Next")
.font(.callout)
.fontWeight(.semibold)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.blue)
.cornerRadius(3)
.foregroundColor(.white)
}
}
}
.transition(.slide)
.animation(.linear, value: self.showNext)
.animation(.linear, value: self.showPrevious)
.padding(.top, 10)
.matchedGeometryEffect(id: positionId, in: self.positionNamespace, properties: .position, anchor: .topTrailing, isSource: false)
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
.animation(.easeOut, value: self.currentStep)
.onAppear {
self.currentStep = 0
}
.onChange(of: self.instructions) { newV in
self.currentStep = 0
}
}
}
internal extension RectanglesTutorial {
func onDone(_ callback: @escaping (_ withNext: Bool) -> ()) -> RectanglesTutorial {
.init(instructions: self.$instructions, onDone: callback)
}
}

View File

@ -0,0 +1,216 @@
//////
////// Demo11.swift
////// FutureFlowDemo
//////
////// Created by Muhand Jumah on 4/23/23.
//////
////
////import SwiftUI
////
////struct Demo11: View {
//// var body: some View {
//// ScrollView {
//// Text (
//// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec et orci metus. Donec consectetur laoreet finibus. Pellentesque at maximus leo. Donec nec elementum metus, ut interdum libero. Cras hendrerit neque non magna sodales, eu lacinia justo laoreet. Phasellus pharetra vehicula nibh, porttitor elementum velit vestibulum id. Pellentesque quis mattis risus, eget feugiat justo. Maecenas a tortor neque. Nullam placerat non mauris eget tristique. Nam auctor dolor sit amet tincidunt pulvinar.\n\nNunc cursus eget quam ut convallis. In hac habitasse platea dictumst. Integer id elit ac tellus vehicula viverra eu ac metus. Cras tempus nisl eget nulla porttitor, placerat sollicitudin justo tincidunt. Phasellus et leo mi. Curabitur convallis malesuada mi tincidunt scelerisque. Sed rutrum ultricies ligula. Pellentesque nisl metus, euismod venenatis dui sit amet, lobortis lacinia quam. Nullam ipsum est, tempus et nisl nec, consectetur fermentum nulla. Fusce velit augue, consectetur nec purus id, maximus bibendum elit. Phasellus ornare odio sit amet erat tristique dapibus. Aliquam erat volutpat. Nam hendrerit diam eget arcu dapibus lacinia. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ligula augue, aliquam nec condimentum quis, consectetur a erat.\nFusce at ipsum leo. Morbi egestas sem sit amet velit vestibulum gravida. Nullam eu finibus lorem. Nam ut lacus turpis. Nullam eu sagittis dui. Ut et condimentum purus. Sed et lectus pretium, imperdiet nulla sit amet, pulvinar enim. Suspendisse blandit tempus varius. Sed at consequat orci. Quisque feugiat malesuada mauris nec bibendum. Aenean condimentum ornare leo, quis porttitor risus malesuada vitae. Duis interdum odio ut scelerisque aliquet. Nam et viverra felis. Nulla eu nisi eu nisi condimentum bibendum at in odio. Aenean ut arcu quam.\nInteger non tristique turpis. Vestibulum non dolor metus. Phasellus odio lorem, consequat quis fermentum nec, faucibus scelerisque orci. Sed id placerat nibh, quis ultrices diam. Sed laoreet quis massa vel tincidunt. Proin maximus accumsan convallis. Morbi sed mi ac turpis condimentum faucibus. Donec eros nunc, dignissim vitae nibh a, pulvinar suscipit mi. Nam libero odio, rhoncus id eleifend sed, porttitor ut metus. Nunc fringilla eget ligula vel dictum. Aenean eget posuere eros. Sed placerat nec sem at faucibus. Sed vehicula sollicitudin lectus, ut consequat quam commodo in.\nIn id facilisis ligula, ut aliquet urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam dictum blandit metus vitae dignissim. Nunc ac orci sed lorem volutpat tincidunt. Nulla vel purus nunc. Duis rutrum lorem et semper scelerisque. Quisque rutrum lorem eu arcu ultricies semper. Mauris auctor vulputate est non aliquam. Vivamus viverra ullamcorper sem, et consectetur est dapibus ut. Aliquam at massa sed lectus hendrerit aliquam in et mi. Sed venenatis tincidunt sem, ut rutrum augue vestibulum vitae. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus hendrerit quam et erat finibus tempor. Donec vehicula ut mi non ullamcorper. Sed lorem elit, hendrerit eu volutpat ut, tincidunt vel leo. "
//// )
//// .fixedSize(horizontal: false, vertical: true)
//// .frame(maxWidth: .infinity, maxHeight: .infinity)
//// }.frame(maxHeight: .infinity).padding()
//// }
////}
////
////struct Demo11_Previews: PreviewProvider {
//// static var previews: some View {
//// Demo11()
//// }
////}
//
//import SwiftUI
//@available(iOS 16.0, *)
//extension Image {
// @MainActor func frame(width: CGFloat, height: CGFloat) -> Image {
// let imageRenderer: ImageRenderer = .init(
// content:
// self
// .resizable()
// .frame(width: width, height: height, alignment: .center)
// )
//
// if let uiImage = imageRenderer.uiImage {
// return Image(uiImage: uiImage)
// } else {
// return
// Image(systemName: "x.circle")
// }
// }
//}
//
//@available(iOS 16.0, *)
//struct Demo11: View {
// var body: some View {
//// ScrollView(.vertical) {
//// VStack(alignment: .leading, spacing: 10) {
//// HStack(alignment: .top, spacing: 10) {
//// Image(systemName: "square.fill")
//// .resizable()
//// .frame(width: 80, height: 80)
//// .foregroundColor(.black)
////
//// Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vulputate euismod. In facilisis diam et mollis consequat jkahdfk ladshfkljahsd fjlkahsd fjlkhasd kjlhasdjk faljk dsfljah sdfjlk hasdklj asdnf msdnf m,.asnf asdjhf ljaskhf lkjasdh fljakshd fljkash. ")
//// }
////
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia, dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum. ")
//// }
//// }
//// .padding()
//
// ScrollView(.vertical) {
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") + Text(Image(systemName: "square.fill")) + Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
// AsyncImage(url: .init(string: "https://1000logos.net/wp-content/uploads/2016/10/Apple-Logo.png")) { img in
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") +
////// Text(img.frame(width: 80, height: 80)) +
//// Text(Image(systemName: "x.circle")) +
//// Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
//
// Text(
// "Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia"
// + Image(systemName: "x.circle")
// + ", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum."
// )
//// let m: Image = img
////// .resizable()
////// .scaledToFill()
////// .frame(width: 80, height: 80)
////
//// Text("TEST") +
//// Text(m.frame(width: 80, height: 80)).font(.system(size: 100)) +
//// Text("test2")
// } placeholder: {
// Text("Loading...")
// }
//
//// HStack(alignment: .top, spacing: 10) {
//// VStack(alignment: .leading, spacing: 10) {
//// Image(systemName: "square.fill")
//// .resizable()
//// .frame(width: 80, height: 80)
//// .foregroundColor(.black)
////
//// Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vulputate euismod. In facilisis diam et mollis consequat jkahdfk ladshfkljahsd fjlkahsd fjlkhasd kjlhasdjk faljk dsfljah sdfjlk hasdklj asdnf msdnf m,.asnf asdjhf ljaskhf lkjasdh fljakshd fljkash. ")
//// }
//// .frame(maxWidth: 80, alignment: .topLeading)
////
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia, dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum. ")
//// }
// }
// .padding()
//
//
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") +
////// Text(img.frame(width: 80, height: 80)) +
//// Text(Image(systemName: "x.circle")).font(.system(size: 20)) +
//// Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
// }
//}
//
////struct DynamicTextModifier: ViewModifier {
//// let imageWidth: CGFloat
//// let text: String
//// let font: Font
////
//// func body(content: Content) -> some View {
//// GeometryReader { geometry in
//// let lineWidth = geometry.size.width - imageWidth
//// let textFragments = splitTextToFitWidth(text: text, lineWidth: lineWidth, font: font)
////
//// VStack(alignment: .leading, spacing: 0) {
//// ForEach(textFragments.indices, id: \.self) { index in
//// if index == 0 {
//// HStack {
//// Spacer(minLength: imageWidth)
//// Text(textFragments[index]).font(font)
//// }
//// } else {
//// Text(textFragments[index]).font(font)
//// }
//// }
//// }
//// }
//// }
////
//// private func splitTextToFitWidth(text: String, lineWidth: CGFloat, font: Font) -> [String] {
//// let words = text.split(separator: " ")
//// var textFragments: [String] = []
//// var currentLine = ""
////
//// for word in words {
//// let newLine = currentLine.isEmpty ? "\(word)" : "\(currentLine) \(word)"
//// let newSize = newLine.size(usingFont: font)
////
//// if newSize.width <= lineWidth {
//// currentLine = newLine
//// } else {
//// textFragments.append(currentLine)
//// currentLine = "\(word)"
//// }
//// }
////
//// if !currentLine.isEmpty {
//// textFragments.append(currentLine)
//// }
////
//// return textFragments
//// }
////}
////
////extension String {
//// func size(usingFont font: Font) -> CGSize {
//// let nsString = self as NSString
//// let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: font.size)] // Adjust this line if you use a custom font
//// return nsString.size(withAttributes: fontAttributes)
//// }
////}
////
////extension Font {
//// var size: CGFloat {
//// switch self {
//// case .largeTitle: return UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
//// case .title: return UIFont.preferredFont(forTextStyle: .title1).pointSize
//// case .title2: return UIFont.preferredFont(forTextStyle: .title2).pointSize
//// case .title3: return UIFont.preferredFont(forTextStyle: .title3).pointSize
//// case .headline: return UIFont.preferredFont(forTextStyle: .headline).pointSize
//// case .subheadline: return UIFont.preferredFont(forTextStyle: .subheadline).pointSize
//// case .body: return UIFont.preferredFont(forTextStyle: .body).pointSize
//// case .callout: return UIFont.preferredFont(forTextStyle: .callout).pointSize
//// case .caption: return UIFont.preferredFont(forTextStyle: .caption1).pointSize
//// case .caption2: return UIFont.preferredFont(forTextStyle: .caption2).pointSize
//// case .footnote: return UIFont.preferredFont(forTextStyle: .footnote).pointSize
//// default: return UIFont.preferredFont(forTextStyle: .body).pointSize
//// }
//// }
////}
////
////import SwiftUI
////
////struct Demo11: View {
//// @State private var dynamicText: String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut dapibus malesuada turpis, non posuere est laoreet nec. Sed maximus bibendum odio, a rhoncus nisl facilisis a. Curabitur vitae consequat justo."
////
//// var body: some View {
//// ZStack(alignment: .topLeading) {
//// Color.clear.modifier(DynamicTextModifier(imageWidth: 80, text: dynamicText, font: .body))
////
//// Image(systemName: "square.fill") // Replace with your own image
//// .resizable()
//// .frame(width: 100, height: 100) // Adjust the frame size to match your image size
//// .background(Color.red) // Optional, just to visualize the image frame
//// }
//// .padding()
//// }
////}
////
//@available(iOS 16.0, *)
//struct Demo11_Previews: PreviewProvider {
// static var previews: some View {
// Demo11()
// }
//}

View File

@ -0,0 +1 @@
**NOTE:** Ignore this directory. Nothing here is important, this directory will be deleted soon.

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:FutureFlow">
</FileRef>
<FileRef
location = "group:FutureFlowDemo/FutureFlowDemo.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,33 +0,0 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FutureFlow",
platforms: [
.iOS(.v14),
.macOS(.v11),
.tvOS(.v14),
.watchOS(.v7)
],
products: [
.library(
name: "FutureFlow",
targets: ["FutureFlow"]
),
],
targets: [
.target(
name: "FutureFlow",
dependencies: [],
path: "Sources"
),
.testTarget(
name: "FutureFlowTests",
dependencies: ["FutureFlow"],
path: "Tests/FutureFlowTests"
),
],
swiftLanguageVersions: [.v5]
)

View File

@ -1,12 +0,0 @@
//
// FutureFlowManager.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
internal class FutureFlowManager {
internal static var currentNamespaces: [String:Namespace.ID] = [:]
}

View File

@ -1,179 +0,0 @@
//
// HighlightingView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
internal struct HighlightingView<Content: View, Chunk: FlowChunk>: View {
// Binding variables
@Binding var showTutorial: Bool
// Private variables
let namespace: Namespace.ID
let chunks: [Chunk]
let content: Content
#if DEBUG
// State variables (TODO: Delete)
@State private var currentIndex: Int = 0
#endif
@Namespace private var namespace2
internal init(namespace: Namespace.ID, showTutorial: Binding<Bool>, chunks: [Chunk], @ViewBuilder content: () -> Content) {
self.namespace = namespace
self._showTutorial = showTutorial
self.chunks = chunks
self.content = content()
}
internal var body: some View {
ZStack {
self.content
SpotlightView(
chunk: self.chunks[self.currentIndex],
namespace: self.namespace,
namespace2: self.namespace2
)
.opacity(self.showTutorial ? 1 : 0)
.animation(
.easeOut,
value: self.showTutorial ? self.currentIndex : nil
)
.overlay(
GeometryReader { reader in
instructionsOverlay(reader: reader)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
// Text("\(reader.size.width) X \(reader.size.height)")
// ZStack {
// Color.blue
// }
}
.matchedGeometryEffect(id: 10000, in: self.namespace2, isSource: false)
// .overlay(
// GeometryReader { reader in
// Text("\(reader.size.width) X \(reader.size.height)")
// }
// )
)
//#if DEBUG
// Button(action: {
// if self.currentIndex < self.chunks.count - 1{
// self.currentIndex += 1
// } else {
// self.currentIndex = 0
// }
// }) {
// Text("NEXT")
// }
//#endif
}
}
}
private extension HighlightingView {
func advance() {
if self.currentIndex < self.chunks.count - 1{
self.currentIndex += 1
} else {
self.currentIndex = 0
}
}
func previous() {
if self.currentIndex > 0 {
self.currentIndex -= 1
} else {
self.currentIndex = self.chunks.count - 1
}
}
@ViewBuilder func instructionsOverlay(reader: GeometryProxy) -> some View {
if let instructionsViewType = self.chunks[self.currentIndex].instructionsViewType {
let v = instructionsViewType
.body(
currentIndex: self.$currentIndex,
maxCount: self.chunks.count,
advance: self.advance,
previous: self.previous
)
ZStack {
// TODO: Find a better way than this hacky method
v
.opacity(0)
}
.overlay(
GeometryReader { rr in
v
.frame(width: UIScreen.main.bounds.size.width)
.offset(
x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
y: self.getYOffset(position: v.position, in: v.position == .below ? reader.size : rr.size)
)
}
)
// v
// .overlay(
// Text("\(reader.size.height)").allowsHitTesting(false))
// .offset(
// x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
// y: self.getYOffset(position: v.position, in: reader.size)
// )
// y: self.getYOffset(position: v.position, in: reader.size)
// )
}
// ZStack {
// if let sle
// if(self.chunks[self.currentIndex].instruction != nil) {
// SimpleInstructionsBubble(text: "TEST")
// .frame(width: 200, height: 100, alignment: .center)
// self.chunks[self.currentIndex].instruction!.bubble.body(instruction: self.chunks[self.currentIndex].instruction!)
// }
// .offset(x: 0, y: reader.size.height + 20)
// if let instruction = self.chunks[self.currentIndex].instruction {
// self
// .chunks[self.currentIndex]
// .instructionsViewType!
//// .inWithEnum()
// .body(adv: self.advance, prev: self.previous)
// .instructionsView(advance: self.advance, previous: self.previous)
// instruction.bubble.body(instruction: instruction)
// .padding(.horizontal, 15)
// .frame(width: UIScreen.main.bounds.size.width)
//// .offset(x: 0, y: 400)
// .offset(
// x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
// y: self.getYOffset(position: instruction.position, in: reader.size)
// )
// }
// if (self.chunk.instructionsBubble != nil) {
// self.chunk.instructionsBubble!.body
// .padding(.horizontal)
// .frame(width: UIScreen.main.bounds.size.width)
// .offset(x: 0, y: self.getYOffset(position: self.chunk.instructionsBubble!.position, in: reader.size))
// }
// }
}
func getYOffset(position: InstructionsViewPosition, in frame: CGSize) -> CGFloat {
switch position {
case .above:
return -(frame.height + 20)
default:
return (frame.height + 20)
}
}
}

View File

@ -1,27 +0,0 @@
//
// InstructionsView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
public struct InstructionsView : View {
internal let position: InstructionsViewPosition
private let content: AnyView
internal init<Content: View>
(
position: InstructionsViewPosition = .below,
@ViewBuilder _ content: () -> Content
) {
self.position = position
self.content = content().eraseToAnyView()
}
@_spi(Private)
public var body: some View {
self.content
}
}

View File

@ -1,136 +0,0 @@
//
// InstructionsViewType.swift
//
//
// Created by Muhand Jumah on 4/26/23.
//
public enum InstructionsViewType {
case simple(instructions: [String], position: InstructionsViewPosition = .below)
case custom(view: (_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> InstructionsView)
internal func body(
currentIndex: Binding<Int>,
maxCount: Int,
advance: @escaping () -> (),
previous: @escaping () -> ()
) -> InstructionsView {
switch self {
case .simple(let instructions, let position):
return SimpleInstructionsViewWrapper(currentGlobalIndex: currentIndex,
globalMaxCount: maxCount,
advance: advance,
previous: previous,
instructions: instructions)
.convertToInstructionsView(position: position)
case .custom(let view):
return view(advance, previous)
}
}
}
// TODO: Rework this piece
import SwiftUI
internal struct SimpleInstructionsViewWrapper: View {
// Global instructions
@Binding var currentGlobalIndex: Int
var globalMaxCount: Int
let advance: () -> ()
let previous: () -> ()
private(set) var instructions: [String]
@State private var currentInstructionIndex: Int = 0
@State private var showPrevious: Bool = false
@State private var showNext: Bool = false
var body: some View {
SimpleInstructionsView(
text: self.instructions[self.currentInstructionIndex],
showPrevious: .init (
get: {
return (self.currentGlobalIndex > 0 && self.globalMaxCount > 0) || (self.currentInstructionIndex > 0 && self.instructions.count > 0)
},
set: { _ in}
),
showNext: .init(
get: {
return self.currentInstructionIndex < instructions.count - 1 || self.currentGlobalIndex < self.globalMaxCount - 1
},
set: { _ in }
)
)
.onTapNext {
guard currentInstructionIndex == instructions.count - 1 else {
currentInstructionIndex += 1
return
}
advance()
self.currentInstructionIndex = 0
}
.onTapPrev {
guard currentInstructionIndex == 0 else {
currentInstructionIndex -= 1
return
}
// guard self.currentInstructionIndex > 0 else {
// return
// }
// self.currentInstructionIndex -= 1
self.currentInstructionIndex = 0
previous()
// self.currentInstructionIndex = instructions.count
}
.overlay(
VStack {
Text("\(self.globalMaxCount)")
Text("\(self.currentGlobalIndex)")
Text("\(self.instructions.count)")
Text("\(self.currentInstructionIndex)")
}
.offset(x: 0, y: 100)
)
.padding(.horizontal, 15)
// .onChange(of: self.currentInstructionIndex, perform: { newValue in
// // For the next button
// if(newValue < instructions.count - 1 || currentGlobalIndex < globalMaxCount - 1) {
// self.showNext = true
// } else {
// self.showNext = false
// }
//
// if(newValue > 0 && self.instructions.count > 0) {
// self.showPrevious = true
// } else {
// self.showPrevious = false
// }
// })
.onAppear {
// Next
if(currentInstructionIndex < instructions.count - 1 || currentGlobalIndex < globalMaxCount) {
self.showNext = true
} else {
self.showNext = false
}
// if(currentInstructionIndex < instructions.count - 1) {
// self.showNext = true
// } else {
// self.showNext = false
// }
//
if(currentInstructionIndex > 0 && self.instructions.count > 0) {
self.showPrevious = true
} else {
self.showPrevious = false
}
}
}
}
//struct SimpleInstructionsViewWrapper_Previews: PreviewProvider {
// static var previews: some View {
// SimpleInstructionsViewWrapper(instructions: ["Hello", "World", "askdlfjalk shfjlka dhfkj ahsdfkhdj"])
// }
//}

View File

@ -1,146 +0,0 @@
//
// SimpleInstructionsView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
struct SimpleInstructionsView: View {
private var text: String
private var nextTapped: () -> ()
private var prevTapped: () -> ()
@Binding var showPrevious: Bool
@Binding var showNext: Bool
public init(
text: String,
showPrevious: Binding<Bool> = .constant(false),
showNext: Binding<Bool> = .constant(false)
) {
self.text = text
self.nextTapped = { }
self.prevTapped = { }
self._showPrevious = showPrevious
self._showNext = showNext
}
fileprivate init(
text: String,
nextTapped: @escaping () -> (),
prevTapped: @escaping () -> (),
showPrevious: Binding<Bool> = .constant(false),
showNext: Binding<Bool> = .constant(false)
) {
self.text = text
self.nextTapped = nextTapped
self.prevTapped = prevTapped
self._showPrevious = showPrevious
self._showNext = showNext
}
var body: some View {
// Color.purple
VStack(alignment:.leading, spacing: 20) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "lightbulb.circle.fill")
.font(.system(size: 32))
.foregroundColor(.blue)
Text(self.text)
.font(.body)
}
if (self.showPrevious || self.showNext) {
HStack(alignment: .center, spacing:15) {
Spacer()
if(self.showPrevious) {
Button(action: {
self.prevTapped()
}) {
Text("Previous")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.blue)
}
}
if(self.showNext) {
Button(action: {
self.nextTapped()
}) {
Text("Next")
.font(.callout)
.fontWeight(.semibold)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.blue)
.cornerRadius(3)
.foregroundColor(.white)
}
}
}
}
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
}
func onTapNext(_ callback: @escaping () -> ()) -> Self {
return .init(
text: self.text,
nextTapped: callback,
prevTapped: self.prevTapped,
showPrevious: self.$showPrevious,
showNext: self.$showNext
)
}
func onTapPrev(_ callback: @escaping () -> ()) -> Self {
return .init(
text: self.text,
nextTapped: self.nextTapped,
prevTapped: callback,
showPrevious: self.$showPrevious,
showNext: self.$showNext
)
}
}
struct SimpleInstructionsView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack(spacing: 5) {
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.")
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showNext: .constant(true))
.onTapNext {
print("Next Tapped")
}
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true))
.onTapPrev {
print("Previous Tapped")
}
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true), showNext: .constant(true))
.onTapNext {
print("Next Tapped")
}
.onTapPrev {
print("Previous Tapped")
}
}
.padding()
}
}
}

View File

@ -1,70 +0,0 @@
//
// SpotlightView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
internal struct SpotlightView<Chunk: FlowChunk>: View {
private var chunk: Chunk
private var namespace: Namespace.ID
private var namespace2: Namespace.ID
internal init(chunk: Chunk, namespace: Namespace.ID, namespace2: Namespace.ID) {
self.chunk = chunk
self.namespace = namespace
self.namespace2 = namespace2
}
internal var body: some View {
ZStack {
self.chunk.spotlightBackground.body(self.namespace)
// GeometryReader { reader in
self.chunk.spotlightShape.body(self.namespace)
.blendMode(.destinationOut)
.matchedGeometryEffect(id: 10000, in: self.namespace2, isSource: true)
// .overlay(
// instructionsOverlay(reader: reader),
// alignment: .top
// )
// }
.matchedGeometryEffect(
id: self.chunk.id,
in: namespace,
properties: .frame, anchor: .center,
isSource: false
)
// .blendMode(.destinationOut)
}
.compositingGroup()
}
}
// MARK: - Helper Methods
//private extension SpotlightView {
// func instructionsOverlay(reader: GeometryProxy) -> some View {
// ZStack {
// if(self.chunk.instructions.count > 0) {
// Text("TEST")
// }
//// if (self.chunk.instructionsBubble != nil) {
//// self.chunk.instructionsBubble!.body
//// .padding(.horizontal)
//// .frame(width: UIScreen.main.bounds.size.width)
//// .offset(x: 0, y: self.getYOffset(position: self.chunk.instructionsBubble!.position, in: reader.size))
//// }
// }
// }
//
// func getYOffset(position: InstructionsBubblePosition, in frame: CGSize) -> CGFloat {
// switch position {
// case .above:
// return -(frame.height + 20)
// default:
// return (frame.height + 20)
// }
// }
//}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,91 +0,0 @@
//
// ContentView.swift
// FutureFlowDemo
//
// Created by Muhand Jumah on 4/21/23.
//
import SwiftUI
import FutureFlow
enum TutorialChunks: FlowChunk, CaseIterable {
case red
case green
case yellow
var spotlightShape: SpotlightShape {
switch self {
case .yellow:
return .circle()
default:
return .rectangle()
}
}
var spotlightBackground: SpotlightBackground {
switch self {
case .green:
return .ultraThinMaterial
default:
return .black
}
}
var instructionsViewType: InstructionsViewType? {
switch self {
case .red:
return .simple(instructions: ["Text1"], position: .below)
case .green:
return .simple(instructions: ["Text2", "Text22", "Text222", "Text2222"], position: .below)
case .yellow:
return .simple(instructions: ["Text3"], position: .above)
}
}
}
struct ContentView: View {
private var uniqueIdentifier: String = UUID().uuidString
private var tutorial: [TutorialChunks] =
[
.red,
.green,
.yellow
]
var body: some View {
VStack(spacing: 50) {
Rectangle()
.frame(width: 256, height: 256, alignment: .center)
.foregroundColor(Color.red)
.configureChunkForSpotlight(
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[0]
)
Rectangle()
.frame(width: 64, height: 128, alignment: .center)
.foregroundColor(Color.green)
.configureChunkForSpotlight(
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[1]
)
Circle()
.frame(width: 128, height: 128, alignment: .center)
.foregroundColor(.yellow)
.configureChunkForSpotlight(
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[2]
)
}
.padding()
.assembleSpotlightChunks(uniqueIdentifier: self.uniqueIdentifier, chunks: Array(TutorialChunks.allCases))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@ -1,19 +0,0 @@
////
//// Demo11.swift
//// FutureFlowDemo
////
//// Created by Muhand Jumah on 4/23/23.
////
//
//import SwiftUI
//
//struct Demo11: View {
//
//}
//
//struct Demo11_Previews: PreviewProvider {
// static var previews: some View {
//
//
// }
//}

View File

@ -21,12 +21,13 @@ let package = Package(
.target(
name: "FutureFlow",
dependencies: [],
path: "FutureFlow/Sources"
path: "Sources",
exclude: ["./Examples"]
),
.testTarget(
name: "FutureFlowTests",
dependencies: ["FutureFlow"],
path: "FutureFlow/Tests/FutureFlowTests"
path: "Tests/FutureFlowTests"
),
],
swiftLanguageVersions: [.v5]

213
README.md
View File

@ -9,3 +9,216 @@ As of **April 22nd, 2023**, there are no plans to support UIKit nor support any
If you however, would like to tackle backward compatibility or UIKit support, feel free to fork this repo and work on it and
hit me up to merge your contribution and credit you.
## Installation
### Swift Package Manager (SPM)
To add FutureFlow to your project, use Xcodes built-in support for Swift packages. Click File → Swift Packages → Add Package Dependency, and paste the following URL into the search field:
```
https://github.com/xyfuture-llc/FutureFlow.git
```
Youre then prompted to select the version to install and indicate the desired update policy. I recommend starting with the latest version (its selected automatically), and choosing “up to next major” as the preferred update rule. Once you click Next, the package is fetched. Then select the target youre going to use FutureFlow in. Click Finish, and youre ready to go.
### CocoaPods
To be released
## Usage
![](https://github.com/xyfuture-llc/FutureFlow/blob/main/Examples/Assets/demo1.gif)
A full example can be found inside the `Examples` directory.
In order to use FutureFlow you first need to create an `enum` that conforms to `FutureFlow` protocol (Conforming to `CaseIterable` is optional but it makes your life easy).
### Step 1
```Swift
enum TutorialChunks: FlowChunk, CaseIterable {
}
```
Inside the enum define the steps in which you want the spotlight to go to.
```Swift
enum TutorialChunks: FlowChunk, CaseIterable {
case red
case green
case yellow
}
```
This is as simple as it gets; However you can customize it further by defining the spotlight shape, the type of background, or even include an instructions view (A view where you instruct the user why are you shining the spot light here).
Here are the defined protocols
```Swift
var spotlightShape: SpotlightShape { get }
var spotlightBackground: SpotlightBackground { get }
@InstructionsViewBuilder func instructionsView(
_ next: @escaping () -> (),
_ back: @escaping () -> ()
) -> AnyInstructionsView?
var instructionsViewPosition: InstructionsViewPosition? { get }
```
Here are the default values if the above is not defined:
- `spotlightShape`: `.rectangle`
- `spotlightBackground`: `.black`
- `instructionsView`: `nil`
- `instructionsViewPosition`: `center`
You can customize a shape or a background by calling the `.custom` enum case such as
```Swift
enum TutorialChunks: FlowChunk, CaseIterable {
case red
case green
case yellow
var spotlightShape: SpotlightShape {
return SpotlightShape.custom {
Circle()
.foregroundColor(.white)
.blur(radius: 3)
}
}
}
```
### Step 2
After creating your enum you should create the view in which you want to shine spotlight on.
```Swift
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Rectangle()
.frame(width: 256, height: 256, alignment: .center)
.foregroundColor(.red)
Rectangle()
.frame(width: 64, height: 128, alignment: .center)
.foregroundColor(Color.green)
Circle()
.frame(width: 128, height: 128, alignment: .center)
.foregroundColor(.yellow)
}
.padding()
}
}
```
Now you need to create a unique identifier for this view in which we will be using as well as a namespace and a variable to control if the spotlight view should be displayed or not (in this case I am calling it a "tutorial")
```Swift
...
private var uniqueIdentifier: String = UUID().uuidString
@State private var showTutorial: Bool = true
@Namespace var namespace
...
```
As can be seen there must be a parent container to wrap the elements to shine a `spotlight` on. In this case I am using `VStack`.
Now you need to call `configureChunkForSpotlight` on each element you want to shine the spotlight on and you need to call `assembleSpotlightChunks` on the wrapper.
#### `configureChunkForSpotlight`
Prepares the current view to be highlighted by the spotlight effect, associating it with the specified `FlowChunk`.
This function should be used to mark individual child views within a parent view that has the `assembleSpotlightChunks` function applied to it.
- Parameters:
- namespace: The namespace that belongs to the view where the animations will be played.
- parentIdentifier: A unique string identifier for the parent view containing the spotlight effect.
- chunk: The `FlowChunk` associated with the current view, which determines the spotlight shape, background, and other properties.
- Returns: The modified view with the matched geometry effect applied, allowing it to be highlighted by the spotlight effect.
#### `assembleSpotlightChunks`
Sets up the parent view to contain the spotlight effect, using the provided `FlowChunk` instances.
This function should be used to wrap the parent view containing child views that have the `configureChunkForSpotlight` function applied to them.
The chunks provided must be of the same `FlowChunk` type as the one used by the child views, to ensure that the spotlight effect works as intended.
- Parameters:
- namespace: The namespace that belongs to the view where the animations will be played.
- uniqueIdentifier: A unique string identifier for the parent view. This identifier must be the same as the one provided to the `configureChunkForSpotlight` function for child views.
- chunks: An array of `FlowChunk` instances associated with the child views to be highlighted by the spotlight effect.
- showTutorial: A boolean variable to toggle the spotlight view on and off.
- onStepChange: An optional callback method that will be triggered when the current step is changed, basically when the spotlight shines on a different element than the current element.
- Returns: The modified view with the `HighlightingView` applied, containing the spotlight effect and its child views.
---
This is how the final view looks like
```Swift
struct ContentView: View {
private var uniqueIdentifier: String = UUID().uuidString
@State private var showTutorial: Bool = true
@Namespace var namespace
var body: some View {
VStack(spacing: 50) {
Rectangle()
.frame(width: 256, height: 256, alignment: .center)
.foregroundColor(.red)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.red
)
Rectangle()
.frame(width: 64, height: 128, alignment: .center)
.foregroundColor(Color.green)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.green
)
Circle()
.frame(width: 128, height: 128, alignment: .center)
.foregroundColor(.yellow)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: TutorialChunks.yellow
)
.onTapGesture {
self.showTutorial = true
}
}
.padding()
.assembleSpotlightChunks(namespace:self.namespace, uniqueIdentifier: self.uniqueIdentifier, chunks: Array(TutorialChunks.allCases), showTutorial: self.$showTutorial) {
chunk in
print("A new step")
}
}
}
```
That's it! This is the whole API.
## More
You can browse teh source files to learn more about the other options available to you. You can also look at "Examples" directory as I will add more examples for different features/options.
## Contributing
Contributions are welcome!
Have a look at [issues](https://github.com/xyfuture-llc/FutureFlow/issues) to see the projects current needs. Dont hesitate to create new issues, especially if you intend to work on them yourself.
If youd like to discuss something else, contact me directly.
## License and Copyright
FutureFlow is licensed under the MIT License. See the [LICENSE file](https://github.com/xyfuture-llc/FutureFlow/blob/main/LICENSE) for details.
© 20232023 xyFuture, LLC

View File

@ -14,12 +14,12 @@ public extension View {
/// This function should be used to mark individual child views within a parent view that has the `assembleSpotlightChunks` function applied to it.
///
/// - Parameters:
/// - namespace: The namespace that belongs to the view where the animations will be played.
/// - parentIdentifier: A unique string identifier for the parent view containing the spotlight effect.
/// - chunk: The `FlowChunk` associated with the current view, which determines the spotlight shape, background, and other properties.
///
/// - Returns: The modified view with the matched geometry effect applied, allowing it to be highlighted by the spotlight effect.
func configureChunkForSpotlight<Chunk: FlowChunk>(parentIdentifier: String, chunk: Chunk) -> some View {
let namespace = getNamespace(from: parentIdentifier)
func configureChunkForSpotlight<Chunk: FlowChunk>(namespace: Namespace.ID, parentIdentifier: String, chunk: Chunk) -> some View {
return self
.matchedGeometryEffect(
@ -38,26 +38,26 @@ public extension View {
/// The chunks provided must be of the same `FlowChunk` type as the one used by the child views, to ensure that the spotlight effect works as intended.
///
/// - Parameters:
/// - namespace: The namespace that belongs to the view where the animations will be played.
/// - uniqueIdentifier: A unique string identifier for the parent view. This identifier must be the same as the one provided to the `configureChunkForSpotlight` function for child views.
/// - chunks: An array of `FlowChunk` instances associated with the child views to be highlighted by the spotlight effect.
/// - showTutorial: A boolean variable to toggle the spotlight view on and off.
/// - onStepChange: An optional callback method that will be triggered when the current step is changed, basically when the spotlight shines on a different element than the current element.
///
/// - Returns: The modified view with the `HighlightingView` applied, containing the spotlight effect and its child views.
func assembleSpotlightChunks<Chunk: FlowChunk>(uniqueIdentifier: String, chunks: [Chunk]) -> some View {
let namespace = getNamespace(from: uniqueIdentifier)
func assembleSpotlightChunks<Chunk: FlowChunk>(namespace: Namespace.ID, uniqueIdentifier: String, chunks: [Chunk], showTutorial: Binding<Bool>, _ onStepChange: ((_ chunk: Chunk) -> ())? = nil) -> some View {
return HighlightingView(
namespace: namespace,
showTutorial: .constant(true),
chunks: chunks
) {
self
}
}
return (
HighlightingView(
namespace: namespace,
showTutorial: showTutorial,
chunks: chunks
) {
self
}
.onStepChange(onStepChange ?? { _ in })
)
func convertToInstructionsView(position: InstructionsViewPosition) -> InstructionsView {
InstructionsView(position: position) {
self
}
}
}
@ -70,17 +70,3 @@ public extension View {
)
}
}
// MARK: - Helper Methods
fileprivate extension View {
func getNamespace(from identifier: String) -> Namespace.ID {
if let existingNamespace = FutureFlowManager.currentNamespaces[identifier] {
return existingNamespace
} else {
let newNamespace = Namespace().wrappedValue
FutureFlowManager.currentNamespaces[identifier] = newNamespace
return newNamespace
}
}
}

View File

@ -11,7 +11,12 @@ public protocol FlowChunk: Hashable, Identifiable {
var spotlightShape: SpotlightShape { get }
var spotlightBackground: SpotlightBackground { get }
var instructionsViewType: InstructionsViewType? { get }
@InstructionsViewBuilder func instructionsView(
_ next: @escaping () -> (),
_ back: @escaping () -> ()
) -> AnyInstructionsView?
var instructionsViewPosition: InstructionsViewPosition? { get }
}
public extension FlowChunk {
@ -27,7 +32,14 @@ public extension FlowChunk {
return .black
}
var instructionsViewType: InstructionsViewType? {
func instructionsView(
_ next: @escaping () -> (),
_ back: @escaping () -> ()
) -> AnyInstructionsView? {
nil
}
var instructionsViewPosition: InstructionsViewPosition? {
return nil
}
}

View File

@ -0,0 +1,131 @@
//
// HighlightingView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
public final class Manager<Chunk: FlowChunk>: ObservableObject {
@Published var totalChunks: [Chunk] = []
@Published var currentStepIndex: Int = 0
}
internal struct HighlightingView<Content: View, Chunk: FlowChunk>: View {
@StateObject var manager: Manager<Chunk> = .init()
// Binding variables
@Binding var showTutorial: Bool
// Private variables
let namespace: Namespace.ID
let chunks: [Chunk]
let content: () -> Content
// #if DEBUG
// State variables (TODO: Delete)
@State private var currentIndex: Int = 0
// #endif
@Namespace private var namespace2
private var onCurrentChunkChanged: ((_ newChunk: Chunk) -> ())?
internal init(
namespace: Namespace.ID,
showTutorial: Binding<Bool>,
chunks: [Chunk],
@ViewBuilder content: @escaping () -> Content)
{
self.namespace = namespace
self._showTutorial = showTutorial
self.chunks = chunks
self.onCurrentChunkChanged = nil
self.content = content
}
fileprivate init(
namespace: Namespace.ID,
showTutorial: Binding<Bool>,
chunks: [Chunk],
onCurrentChunkChanged: @escaping (_ chunk: Chunk) -> (),
content: @escaping () -> Content)
{
self.namespace = namespace
self._showTutorial = showTutorial
self.chunks = chunks
self.onCurrentChunkChanged = onCurrentChunkChanged
self.content = content
}
internal var body: some View {
ZStack {
self.content()
ZStack {
if (self.showTutorial) {
let chunk = self.chunks[self.currentIndex]
SpotlightView(
chunk: chunk,
namespace: self.namespace,
namespace2: self.namespace2
)
.opacity(self.showTutorial ? 1 : 0)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
chunk.instructionsView(self.advance, self.previous)
.frame(width:UIScreen.main.bounds.width * 0.8)
.offset(x:0, y:self.chunks[self.currentIndex].instructionsViewPosition == .below ? 20 : -20)
.matchedGeometryEffect(id: 10001,
in: self.namespace2,
properties: .position,
anchor: self.chunks[self.currentIndex].instructionsViewPosition == .below ? .top : .bottom,
isSource: false)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
}
}
.transition(.opacity)
.animation(.linear, value: self.showTutorial)
}
.onChange(of: self.showTutorial) { newValue in
if(newValue == false) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.currentIndex = 0
}
}
}
.onChange(of: self.currentIndex) { newValue in
if let callback = self.onCurrentChunkChanged {
callback(self.chunks[newValue])
}
}
}
func onStepChange(_ callback: @escaping (_ chunk: Chunk) -> ()) -> HighlightingView {
.init(namespace: self.namespace,
showTutorial: self.$showTutorial,
chunks: self.chunks,
onCurrentChunkChanged: callback,
content: self.content)
}
}
private extension HighlightingView {
func advance() {
if self.currentIndex < self.chunks.count - 1{
self.currentIndex += 1
return
}
self.showTutorial = false
}
func previous() {
if self.currentIndex > 0 {
self.currentIndex -= 1
}
}
}

View File

@ -0,0 +1,23 @@
//
// AnyInstructionsView.swift
//
//
// Created by Muhand Jumah on 4/29/23.
//
import SwiftUI
public struct AnyInstructionsView: InstructionsView {
private let _view: () -> AnyView
public var body: some View {
_view()
}
public init<I: InstructionsView>(_ instructionsView: I) {
_view = {
instructionsView
.eraseToAnyView()
}
}
}

View File

@ -0,0 +1,19 @@
//
// InstructionsView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
public protocol InstructionsView: View {
}
extension InstructionsView {
func eraseToAnyInstructionsView() -> AnyInstructionsView {
AnyInstructionsView(
self
)
}
}

View File

@ -0,0 +1,26 @@
//
// InstructionsViewBuilder.swift
//
//
// Created by Muhand Jumah on 4/29/23.
//
import Foundation
@resultBuilder
public struct InstructionsViewBuilder {
public static func buildBlock<Content: InstructionsView>(_ instructionsView: Content?) -> AnyInstructionsView? {
instructionsView?
.eraseToAnyInstructionsView()
}
public static func buildEither<Content: InstructionsView>(first component: Content?) -> AnyInstructionsView? {
component?
.eraseToAnyInstructionsView()
}
public static func buildEither<Content: InstructionsView>(second component: Content?) -> AnyInstructionsView? {
component?
.eraseToAnyInstructionsView()
}
}

View File

@ -0,0 +1,128 @@
////
//// InstructionsViewType.swift
////
////
//// Created by Muhand Jumah on 4/26/23.
////
//
//public enum InstructionsViewType {
//// case simple(instructions: [String], position: InstructionsViewPosition = .below)
//// case custom(view: (_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> InstructionsView)
// case custom(view:(_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> AnyInstructionsView)
////
//// internal func body(
//// currentIndex: Binding<Int>,
//// maxCount: Int,
//// advance: @escaping () -> (),
//// previous: @escaping () -> ()
//// ) -> InstructionsView {
//// switch self {
//// case .simple(let instructions, let position):
//// return SimpleInstructionsViewWrapper(currentGlobalIndex: currentIndex,
//// globalMaxCount: maxCount,
//// advance: advance,
//// previous: previous,
//// instructions: instructions)
//// .convertToInstructionsView(position: position)
//// case .custom(let view):
//// return view(advance, previous)
//// }
//// }
//}
//
////// TODO: Rework this piece
////import SwiftUI
////
////internal struct SimpleInstructionsViewWrapper: View {
//// // Global instructions
//// @Binding var currentGlobalIndex: Int
//// var globalMaxCount: Int
//// let advance: () -> ()
//// let previous: () -> ()
////
//// private(set) var instructions: [String]
//// @State private var currentInstructionIndex: Int = 0
//// @State private var showPrevious: Bool = false
//// @State private var showNext: Bool = false
////
//// var body: some View {
//// SimpleInstructionsView(
//// text: self.instructions[self.currentInstructionIndex],
//// showPrevious: .init (
//// get: {
//// return (self.currentGlobalIndex > 0 && self.globalMaxCount > 0) || (self.currentInstructionIndex > 0 && self.instructions.count > 0)
//// },
//// set: { _ in}
//// ),
//// showNext: .init(
//// get: {
//// return self.currentInstructionIndex < instructions.count || self.currentGlobalIndex < self.globalMaxCount
//// },
//// set: { _ in }
//// )
//// )
//// .onTapNext {
//// guard currentInstructionIndex == instructions.count - 1 else {
//// currentInstructionIndex += 1
//// return
//// }
////
//// advance()
//// self.currentInstructionIndex = 0
//// }
//// .onTapPrev {
//// guard currentInstructionIndex == 0 else {
//// currentInstructionIndex -= 1
//// return
//// }
////// guard self.currentInstructionIndex > 0 else {
////// return
////// }
////// self.currentInstructionIndex -= 1
//// self.currentInstructionIndex = 0
//// previous()
////// self.currentInstructionIndex = instructions.count
////
//// }
//// .padding(.horizontal, 15)
////// .onChange(of: self.currentInstructionIndex, perform: { newValue in
////// // For the next button
////// if(newValue < instructions.count - 1 || currentGlobalIndex < globalMaxCount - 1) {
////// self.showNext = true
////// } else {
////// self.showNext = false
////// }
//////
////// if(newValue > 0 && self.instructions.count > 0) {
////// self.showPrevious = true
////// } else {
////// self.showPrevious = false
////// }
////// })
//// .onAppear {
//// // Next
//// if(currentInstructionIndex < instructions.count - 1 || currentGlobalIndex < globalMaxCount) {
//// self.showNext = true
//// } else {
//// self.showNext = false
//// }
////// if(currentInstructionIndex < instructions.count - 1) {
////// self.showNext = true
////// } else {
////// self.showNext = false
////// }
//////
//// if(currentInstructionIndex > 0 && self.instructions.count > 0) {
//// self.showPrevious = true
//// } else {
//// self.showPrevious = false
//// }
//// }
//// }
////}
////
//////struct SimpleInstructionsViewWrapper_Previews: PreviewProvider {
////// static var previews: some View {
////// SimpleInstructionsViewWrapper(instructions: ["Hello", "World", "askdlfjalk shfjlka dhfkj ahsdfkhdj"])
////// }
//////}

View File

@ -0,0 +1,147 @@
// TODO: Create a default view
////
//// SimpleInstructionsView.swift
////
////
//// Created by Muhand Jumah on 4/23/23.
////
//
//import SwiftUI
//
//struct SimpleInstructionsView: View {
// private var text: String
// private var nextTapped: () -> ()
// private var prevTapped: () -> ()
//
// @Binding var showPrevious: Bool
// @Binding var showNext: Bool
//
// public init(
// text: String,
// showPrevious: Binding<Bool> = .constant(false),
// showNext: Binding<Bool> = .constant(false)
// ) {
// self.text = text
// self.nextTapped = { }
// self.prevTapped = { }
// self._showPrevious = showPrevious
// self._showNext = showNext
// }
//
// fileprivate init(
// text: String,
// nextTapped: @escaping () -> (),
// prevTapped: @escaping () -> (),
// showPrevious: Binding<Bool> = .constant(false),
// showNext: Binding<Bool> = .constant(false)
// ) {
// self.text = text
// self.nextTapped = nextTapped
// self.prevTapped = prevTapped
// self._showPrevious = showPrevious
// self._showNext = showNext
// }
//
// var body: some View {
//// Color.purple
// VStack(alignment:.leading, spacing: 20) {
// HStack(alignment: .top, spacing: 10) {
// Image(systemName: "lightbulb.circle.fill")
// .font(.system(size: 32))
// .foregroundColor(.blue)
//
// Text(self.text)
// .font(.body)
// }
//
// if (self.showPrevious || self.showNext) {
// HStack(alignment: .center, spacing:15) {
// Spacer()
// if(self.showPrevious) {
// Button(action: {
// self.prevTapped()
// }) {
// Text("Previous")
// .font(.callout)
// .fontWeight(.medium)
// .foregroundColor(.blue)
// }
// }
//
// if(self.showNext) {
//
// Button(action: {
// self.nextTapped()
// }) {
// Text("Next")
// .font(.callout)
// .fontWeight(.semibold)
// .padding(.horizontal, 10)
// .padding(.vertical, 5)
// .background(Color.blue)
// .cornerRadius(3)
// .foregroundColor(.white)
// }
// }
// }
// }
// }
// .padding(.horizontal, 20)
// .padding(.vertical, 15)
// .background(Color.white)
// .cornerRadius(10)
// .shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
// }
//
// func onTapNext(_ callback: @escaping () -> ()) -> Self {
// return .init(
// text: self.text,
// nextTapped: callback,
// prevTapped: self.prevTapped,
// showPrevious: self.$showPrevious,
// showNext: self.$showNext
// )
// }
//
// func onTapPrev(_ callback: @escaping () -> ()) -> Self {
// return .init(
// text: self.text,
// nextTapped: self.nextTapped,
// prevTapped: callback,
// showPrevious: self.$showPrevious,
// showNext: self.$showNext
// )
// }
//}
//
//struct SimpleInstructionsView_Previews: PreviewProvider {
// static var previews: some View {
// ZStack {
// Color.white
// .edgesIgnoringSafeArea(.all)
//
// VStack(spacing: 5) {
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.")
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showNext: .constant(true))
// .onTapNext {
// print("Next Tapped")
// }
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true))
// .onTapPrev {
// print("Previous Tapped")
// }
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true), showNext: .constant(true))
// .onTapNext {
// print("Next Tapped")
// }
// .onTapPrev {
// print("Previous Tapped")
// }
// }
// .padding()
// }
// }
//}

View File

@ -0,0 +1,46 @@
//
// SpotlightView.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
internal struct SpotlightView<Chunk: FlowChunk>: View {
private var chunk: Chunk
private var namespace: Namespace.ID
private var namespace2: Namespace.ID
internal init(chunk: Chunk, namespace: Namespace.ID, namespace2: Namespace.ID) {
self.chunk = chunk
self.namespace = namespace
self.namespace2 = namespace2
}
internal var body: some View {
ZStack {
self.chunk.spotlightBackground.body(self.namespace)
self.chunk.spotlightShape.body(self.namespace)
.blendMode(.destinationOut)
.matchedGeometryEffect(
id: 10001,
in: self.namespace2,
properties: .position,
anchor: self.chunk.instructionsViewPosition == .above ? .top : .bottom,
isSource: true
)
.matchedGeometryEffect(
id: self.chunk.id,
in: namespace,
properties: .frame,
anchor: .center,
isSource: false
)
}
.compositingGroup()
}
}