Skip to content

Commit

Permalink
Merge pull request #143 from AWSary/aai-planner
Browse files Browse the repository at this point in the history
Aai planner
  • Loading branch information
tigpt authored Jun 13, 2024
2 parents eb0029f + 16352ab commit 9839835
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 9 deletions.
123 changes: 123 additions & 0 deletions ios/AWSaryAppClip/AAIplannerContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import SwiftUI
import EventKit
import StoreKit
import RevenueCat

struct AAIplannerContentView: View {
@StateObject private var viewModel = CalendarViewModel()
@State private var selectedEventName = "Architecting on AWS"
@State private var startDate = Date()
@State private var selectedTimeZone = TimeZone.current.identifier
@State private var accessGranted = false
@ObservedObject var userModel = UserViewModel.shared
@State private var showAlert = false

let eventNames = AAIEventData.eventNames
let eventSequences = AAIEventData.eventSequences
let timeZones = AAIEventData.timeZones

var body: some View {
VStack {
Spacer()
Text("AAI Planner")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.top)
.padding(.bottom)

Text("If you are an AWS Authorized Instructor (AAI), you can easily add to your calendar the plans for your training deliverability. Select the training, the start time, and the time zone, and press Add Events.\n\nRecommended Modules plans and breaks will be added to your calendar.")
.font(.body)
.multilineTextAlignment(.center)
.padding([.leading, .trailing, .bottom])

if accessGranted {
Picker("Event Name", selection: $selectedEventName) {
ForEach(eventNames, id: \.self) {
Text($0)
}
}
.pickerStyle(MenuPickerStyle())
.padding()

DatePicker("Start Date and Time", selection: $startDate)
.padding()

Picker("Time Zone", selection: $selectedTimeZone) {
ForEach(timeZones) { timeZone in
Text(timeZone.displayName).tag(timeZone.id)
}
}
.pickerStyle(MenuPickerStyle())
.padding()

if selectedEventName == "Architecting on AWS" || selectedEventName == "Developing on AWS" {
Button("Add Events to Calendar") {
if let sequence = eventSequences[selectedEventName] {
if let timeZone = TimeZone(identifier: selectedTimeZone) {
viewModel.addEvents(sequence: sequence, startDate: startDate, timeZone: timeZone)
showAlert = true
}
}
}.padding()
.alert(isPresented: $showAlert) {
Alert(
title: Text("Events Added"),
message: Text("All your events added scucessfully. 🥳" +
"Enjoy your training day")
)
}
} else {
Text("Please install App for full access").padding()
}
Spacer()
Label {
VStack(alignment: .leading){
Text("Send Feedback")
Text("Feedback emails are lovely to read!").font(.footnote).opacity(0.6)
}
} icon:{
Image(systemName: "envelope")
}.onTapGesture {
let address = "[email protected]"
let subject = "Feedback on AWSary"

// Example email body with useful info for bug reports
let body = "\n\n--\nAWSary Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild))\n\nScreen: AAI Planner"

// Build the URL from its components
var components = URLComponents()
components.scheme = "mailto"
components.path = address
components.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body)
]

guard let email_url = components.url else {
NSLog("Failed to create mailto URL")
return
}
UIApplication.shared.open(email_url) { success in
// handle success or failure
}
}
} else {
Spacer()
Text("Requesting Calendar Access...")
.onAppear {
viewModel.requestAccess { granted in
accessGranted = granted
}
}
Spacer()
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
AAIplannerContentView()
}
}

117 changes: 117 additions & 0 deletions ios/Shared/AAIplanner/AAIEventData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import SwiftUI
import Foundation

struct AAIEventData {
@ObservedObject var userModel = UserViewModel.shared

static let eventNames = [
// "Debug",
// "AWS Cloud Essentials for Business Leaders",
// "AWS Cloud Essentials for Business Leaders – Financial Services",
"AWS Cloud Practitioner Essentials",
// "AWS Technical Essentials",
// "AWS Security Essentials",
"Architecting on AWS",
// "AWS Cloud Financial Management for Builders",
// "AWS Cloud for Finance Professionals",
// "AWS Security Best Practices",
// "AWS Security Governance at Scale",
// "AWS Well-Architected Best Practices",
// "Advanced AWS Well-Architected Best Practices",
// "Big Data on AWS",
// "Building Batch Data Analytics Solutions on AWS",
// "Building Data Analytics Solutions Using Amazon Redshift",
// "Building Data Lakes on AWS",
// "Building Streaming Data Analytics on AWS",
// "Cloud Operations on AWS",
// "Data Warehousing on AWS",
// "Deep Learning on AWS",
// "Developing on AWS",
// "Developing Serverless Solutions on AWS",
// "DevOps Engineering on AWS",
// "Migrating to AWS",
// "MLOps Engineering on AWS",
// "Planning and Designing Databases on AWS",
// "Practical Data Science with Amazon SageMaker",
// "Running Containers on Amazon Elastic Kubernetes Service",
// "Security Engineering on AWS",
// "The Machine Learning Pipeline on AWS",
// "Video Streaming Essentials for AWS Media Services",
// "Advanced Architecting on AWS",
// "Advanced Developing on AWS",
// "Architecting on AWS - Accelerator",
// "Amazon SageMaker Studio for Data Scientists",
// "Authoring Visual Analytics Using Amazon QuickSight",
]

static let eventSequences: [String: [(name: String, duration: Int)]] = [
"Debug": [("Debug - Module 0", 20), ("Debug - Module 1", 45)],
"AWS Cloud Practitioner Essentials": [
("CP - Welcome", 20),
("CP - Module 0: Course Introduction", 5),
("CP - Module 1: Introduction to Amazon Web Services", 30),
("CP - Module 2: Compute in the Cloud (Part 1)", 30),
("CP - ☕️🍎 Break", 15),
("CP - Module 2: Compute in the Cloud (Part 2)", 30),
("CP - Module 3: Global Infrastructure and Reliability", 25),
("CP - Module 4: Networking (Part 1)", 20),
("CP - 🍴🍲🥝 Lunch", 60),
("CP - Module 4: Networking (Part 2)", 15),
("CP - Module 5: Storage and Databases", 40),
("CP - Module 6: Security", 35),
("CP - ☕️🍎 Break", 10),
("CP - Module 7: Monitoring and Analytics", 20),
("CP - Module 8: Pricing and Support", 30),
("CP - Module 9: Migration and Innovation", 40),
("CP - Module 10: AWS Certified Cloud Practitioner Basics", 15),
("CP - 🙋‍♀️❓🙋‍♂️ Q&A", 30),
],
"Architecting on AWS": [
("AoA - Module 0: Course Introduction", 30),
("AoA - Module 1: Architecting Fundamentals", 45),
("AoA - ☕️🍎 Break", 15),
("AoA - 🔬🛠️ Lab 1: Explore and interact with the AWS Management Console and AWS Command Line Interface", 35),
("AoA - Module 2: Account Security", 60),
("AoA - ☕️🍎 Break", 15),
("AoA - Module 3: Networking 1", 60),
("AoA - 🍴🍲🥝 Lunch", 60),
("AoA - Module 4: Compute", 75),
("AoA - ☕️🍎 Break", 15),
("AoA - 🔬🛠️ Lab 2: Build your Amazon VPC infrastructure", 45),
("AoA - 🙋‍♀️❓🙋‍♂️ Q&A", 15),
("nextDay",1),

("AoA - Day 1 review", 10),
("AoA - Module 5: Storage", 70),
("AoA - ☕️🍎 Break", 15),
("AoA - Module 6: Database Services", 70),
("AoA - 🔬🛠️ Lab 3: Create a database layer in your Amazon VPC infrastructure", 45),
("AoA - 🍴🍲🥝 Lunch", 60),
("AoA - Module 7: Monitoring and Scaling", 70),
("AoA - 🔬🛠️ Lab 4: Configure high availability in your Amazon VPC", 45),
("AoA - ☕️🍎 Break", 15),
("AoA - Module 8: Automation", 30),
("AoA - Module 9: Containers", 40),
("AoA - 🙋‍♀️❓🙋‍♂️ Q&A", 15),
("nextDay",2),

("AoA - Day 2 review", 10),
("AoA - Module 10: Networking 2", 45),
("AoA - Module 11: Serverless", 45),
("AoA - ☕️🍎 Break", 15),
("AoA - 🔬🛠️ Lab 5: Build a serverless architecture", 45),
("AoA - Module 12: Edge Services", 60),
("AoA - 🍴🍲🥝 Lunch", 60),
("AoA - 🔬🛠️ Lab 6: Configure an Amazon CloudFront distribution with an Amazon S3 origin", 60),
("AoA - Module 13: Backup and Recovery", 40),
("AoA - 🔬🛠️ Capstone lab: Build an AWS multi-tier architecture", 90),
("AoA - Module 14: Course Summary", 10),
("AoA - 🙋‍♀️❓🙋‍♂️ Q&A", 15),
],
// "Advanced Architecting on AWS": [("AAoA - Module 0", 20), ("AAoA - Module 1", 45), ("AAoA - Module 2", 20)],
// "Developing on AWS": [("DoA - Module 0", 20), ("DoA - Module 1", 10), ("DoA - Module 2", 120)],
// "Advanced Developing on AWS": [("ADoA - Module 0", 20), ("ADoA - Module 1", 10), ("ADoA - Module 2", 120)]
]

static let timeZones: [TimeZoneInfo] = TimeZone.knownTimeZoneIdentifiers.sorted().map { TimeZoneInfo(identifier: $0) }
}
135 changes: 135 additions & 0 deletions ios/Shared/AAIplanner/AAIplannerContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import SwiftUI
import EventKit
import StoreKit
import RevenueCat

struct AAIplannerContentView: View {
@StateObject private var viewModel = CalendarViewModel()
@State private var selectedEventName = "Architecting on AWS"
@State private var startDate = Date()
@State private var selectedTimeZone = TimeZone.current.identifier
@State private var accessGranted = false
@ObservedObject var userModel = UserViewModel.shared
@State private var showAlert = false
@State var displayPaywall: Bool = false

let eventNames = AAIEventData.eventNames
let eventSequences = AAIEventData.eventSequences
let timeZones = AAIEventData.timeZones

var body: some View {
VStack {
Spacer()
Text("AAI Planner")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.top)
.padding(.bottom)

Text("If you are an AWS Authorized Instructor (AAI), you can easily add to your calendar the plans for your training deliverability. Select the training, the start time, and the time zone, and press Add Events.\n\nRecommended Modules plans and breaks will be added to your calendar.")
.font(.body)
.multilineTextAlignment(.center)
.padding([.leading, .trailing, .bottom])

if accessGranted {
Picker("Event Name", selection: $selectedEventName) {
ForEach(eventNames, id: \.self) {
if self.userModel.subscriptionActive {
Text($0)
}else{
Text((try! $0.contains(Regex("(^Architecting on AWS$)|(^.*Essentials$)") ) )
? "✅ Free - \($0)" : "🔒 Premium - \($0)")
}
}
}
.pickerStyle(MenuPickerStyle())
.padding()

DatePicker("Start Date and Time", selection: $startDate)
.padding()

Picker("Time Zone", selection: $selectedTimeZone) {
ForEach(timeZones) { timeZone in
Text(timeZone.displayName).tag(timeZone.id)
}
}
.pickerStyle(MenuPickerStyle())
.padding()

let isFree = try! selectedEventName.contains(Regex("(^Architecting on AWS$)|(^.*Essentials$)"))
if self.userModel.subscriptionActive || isFree {
Button("Add Events to Calendar") {
if let sequence = eventSequences[selectedEventName] {
if let timeZone = TimeZone(identifier: selectedTimeZone) {
viewModel.addEvents(sequence: sequence, startDate: startDate, timeZone: timeZone)
showAlert = true
}
}
}.padding()
.alert(isPresented: $showAlert) {
Alert(
title: Text("Events Added"),
message: Text("All your events added scucessfully. 🥳" +
"Enjoy your training day")
)
}
} else {
Button("Unlock Premium to Add Event") {
self.displayPaywall.toggle()
}.padding()
}
Spacer()
Label {
VStack(alignment: .leading){
Text("Send Feedback")
Text("Feedback emails are lovely to read!\nMissing a training? Email me and I will prioritize it.").font(.footnote).opacity(0.6)
}
} icon:{
Image(systemName: "envelope")
}.onTapGesture {
let address = "[email protected]"
let subject = "Feedback on AWSary"

// Example email body with useful info for bug reports
let body = "\n\n--\nAWSary Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild))\n\nScreen: AAI Planner"

// Build the URL from its components
var components = URLComponents()
components.scheme = "mailto"
components.path = address
components.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body)
]

guard let email_url = components.url else {
NSLog("Failed to create mailto URL")
return
}
UIApplication.shared.open(email_url) { success in
// handle success or failure
}
}
} else {
Spacer()
Text("Requesting Calendar Access...")
.onAppear {
viewModel.requestAccess { granted in
accessGranted = granted
}
}
Spacer()
}
}
.sheet(isPresented: $displayPaywall) {
PaywallView(isPresented: .constant(true))
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
AAIplannerContentView()
}
}

Loading

0 comments on commit 9839835

Please sign in to comment.